System Verilog Full Material From Testbench
System Verilog Full Material From Testbench
INTRODUCTION
SystemVerilog is a combined Hardware Description Language and Hardware Verification
Language based on extensions to Verilog. SystemVerilog was created by the donation of the
Superlog language to Accellera in 2002. The bulk of the verification functionality is based on the
OpenVera language donated by Synopsys. In 2005, SystemVerilog was adopted as IEEE
Standard 1800-2005 .Few of SystemVerilog's capabilities are unique, but it is significant that
these capabilities are combined and offered within a single HDL. There is great value in a
common HDL which handles all aspects of the design and verification flow: design description,
functional simulation, property specification, and formal verification.
DATA TYPES :SystemVerilog adds extended and new data types to Verilog for better
encapsulation and compactness. SystemVerilog extends Verilog by introducing C like data
types. SystemVerilog adds a new 2-state data types that can only have bits with 0 or 1 values
unlike verilog 4-state data types which can have 0, 1, X and Z. SystemVerilog also allows user
to define new data types. SystemVerilog offers several data types, representing a hybrid of both
Verilog and C data types. SystemVerilog 2-state data types can simulate faster, take less
memory, and are preferred in some design styles. Then a 4-state value is automatically converted
to a 2-state value, X and Z will be converted to zeros.
TIP : If you don't need the x and z values then use the SystemVerilog int and bit types which
make execution faster.
Signed And Unsigned :
Integer types use integer arithmetic and can be signed or unsigned.The data types byte, shortint,
int, integer, and longint default to signed. The data types bit, reg, and logic default to unsigned,
as do arrays of these types.
To use these types as unsigned, user has to explicitly declare it as unsigned.
int unsigned ui;
int signed si
byte unsigned ubyte;
User can cast using signed and unsigned casting.
if (signed'(ubyte)< 150) // ubyte is unsigned
Void :
The void data type represents nonexistent data. This type can be specified as the return type of
functions to indicate no return value.
void = function_call();
LITERALS
Integer And Logic Literals
In verilog , to assign a value to all the bits of vector, user has to specify them explicitly.
reg[31:0] a = 32'hffffffff;
Systemverilog Adds the ability to specify unsized literal single bit values with a preceding (').'0,
'1, 'X, 'x, 'Z, 'z // sets all bits to this value.
reg[31:0] a = '1;
'x is equivalent to Verilog-2001 'bx
'z is equivalent to Verilog-2001 'bz
'1 is equivalent to making an assignment of all 1's
'0 is equivalent to making an assignment of 0
Time Literals
Time is written in integer or fixed-point format, followed without a space by a time unit (fs ps ns
us ms s step).
EXAMPLE
0.1ns
40ps
The time literal is interpreted as a realtime value scaled to the current time unit and rounded to
the current time precision.
Array Literals
Array literals are syntactically similar to C initializers, but with the replicate operator ( {{}} )
allowed.
EXAMPLE
STRINGS
In Verilog, string literals are packed arrays of a width that is a multiple of 8 bits which hold
ASCII values. In Verilog, if a string is larger than the destination string variable, the string is
truncated to the left, and the leftmost characters will be lost. SystemVerilog adds new keyword
"string" which is used to declare string data types unlike verilog. String data types can be of
arbitrary length and no truncation occurs.
string myName = "TEST BENCH";
String Methods :
SystemVerilog also includes a number of special methods to work with strings. These methods
use the built-in method notation. These methods are:
1. str.len() returns the length of the string, i.e., the number of characters in the string.
2. str.putc(i, c) replaces the ith character in str with the given integral value.
3. str.getc(i) returns the ASCII code of the ith character in str.
4. str.toupper() returns a string with characters in str converted to uppercase.
5. str.tolower() returns a string with characters in str converted to lowercase.
6. str.compare(s) compares str and s, and return value. This comparison is case sensitive.
7. str.icompare(s) compares str and s, and return value .This comparison is case insensitive.
8. str.substr(i, j) returns a new string that is a substring formed by index i through j of str.
9. str.atoi() returns the integer corresponding to the ASCII decimal representation in str.
10. str.atoreal() returns the real number corresponding to the ASCII decimal representation in
str.
11. str.itoa(i) stores the ASCII decimal representation of i into str (inverse of atoi).
12. str.hextoa(i) stores the ASCII hexadecimal representation of i into str (inverse of atohex).
13. str.bintoa(i) stores the ASCII binary representation of i into str (inverse of atobin).
14. str.realtoa(r) stores the ASCII real representation of r into str (inverse of atoreal)
RESULTS :
5
test
BENCH
-18
-32
ST
111
String Pattren Match
Use the following method for pattern matching in SystemVerilog. Match method which is in
OpenVera or C , is not available in SystemVerilog . For using match method which is in C , use
the DPI calls . For native SystemVerilog string match method, hear is the example.
CODE:
function match(string s1,s2);
int l1,l2;
l1 = s1.len();
l2 = s2.len();
match = 0 ;
if( l2 > l1 )
return 0;
for(int i = 0;i < l1 - l2 + 1; i ++)
if( s1.substr(i,i+l2 -1) == s2)
return 1;
endfunction
EXAMPLE:
program main;
string str1,str2;
int i;
initial
begin
str1 = "this is first string";
str2 = "this";
if(match(str1,str2))
$display(" str2 : %s : found in :%s:",str2,str1);
str1 = "this is first string";
str2 = "first";
if(match(str1,str2))
$display(" str2 : %s : found in :%s:",str2,str1);
str1 = "this is first string";
str2 = "string";
if(match(str1,str2))
$display(" str2 : %s : found in :%s:",str2,str1);
Equality
Syntax : Str1 == Str2
Checks whether the two strings are equal. Result is 1 if they are equal and 0 if they are not. Both
strings can be of type string. Or one of them can be a string literal. If both operands are string
literals, the operator is the same Verilog equality operator as for integer types.
EXAMPLE
program main;
initial
begin
string str1,str2,str3;
str1 = "TEST BENCH";
str2 = "TEST BENCH";
str3 = "test bench";
if(str1 == str2)
$display(" Str1 and str2 are equal");
else
$display(" Str1 and str2 are not equal");
if(str1 == str3)
$display(" Str1 and str3 are equal");
else
$display(" Str1 and str3 are not equal");
end
endprogram
RESULT
Str1 and str2 are equal
Str1 and str3 are not equal
Inequality.
Syntax: Str1 != Str2
Logical negation of Equality operator. Result is 0 if they are equal and 1 if they are not. Both
strings can be of type string. Or one of them can be a string literal. If both operands are string
literals, the operator is the same Verilog equality operator as for integer types.
EXAMPLE
program main;
initial
begin
string str1,str2,str3;
str1 = "TEST BENCH";
str2 = "TEST BENCH";
str3 = "test bench";
if(str1 != str2)
$display(" Str1 and str2 are not equal");
else
$display(" Str1 and str2 are equal");
if(str1 != str3)
$display(" Str1 and str3 are not equal");
else
$display(" Str1 and str3 are equal");
end
endprogram
RESULT
EXAMPLE
program main;
initial
begin
string Str1,Str2,Str3;
Str1 = "c";
Str2 = "d";
Str3 = "e";
if(Str1 < Str2)
$display(" Str1 < Str2 ");
if(Str1 <= Str2)
$display(" Str1 <= Str2 ");
if(Str3 > Str2)
$display(" Str3 > Str2");
if(Str3 >= Str2)
$display(" Str3 >= Str2");
end
endprogram
RESULT
EXAMPLE
program main;
initial
begin
string Str1,Str2,Str3,Str4,Str5;
Str1 = "WWW.";
Str2 = "TEST";
Str3 = "";
Str4 = "BENCH";
Str5 = ".IN";
$display(" %s ",{Str1,Str2,Str3,Str4,Str5});
end
endprogram
RESULT
WWW.TESTBENCH.IN
Replication.
Syntax : {multiplier{Str}}
Str can be of type string or a string literal. Multiplier must be of integral type and can be
nonconstant. If multiplier is nonconstant or Str is of type string, the result is a string containing N
concatenated copies of Str, where N is specified by the multiplier. If Str is a literal and the
multiplier is constant, the expression behaves like numeric replication in Verilog (if the result is
used in another expression involving string types, it is implicitly converted to the string type).
EXAMPLE
program main;
initial
begin
string Str1,Str2;
Str1 = "W";
Str2 = ".TESTBENCH.IN";
$display(" %s ",{{3{Str1}},Str2});
end
endprogram
RESULT
WWW.TESTBENCH.IN
Indexing.
Syntax: Str[index]
Returns a byte, the ASCII code at the given index. Indexes range from 0 to N-1, where N is the
number of characters in the string. If given an index out of range, returns 0. Semantically
equivalent to Str.getc(index)
EXAMPLE
program main;
initial
begin
string Str1;
Str1 = "WWW.TESTBENCH.IN";
for(int i =0 ;i < 16 ; i++)
$write("%s ",Str1[i]);
end
endprogram
RESULT
WWW.TESTBENCH.IN
USERDEFINED DATATYPES
Systemverilog allos the user to define datatypes. There are different ways to define user defined
datatypes. They are
1. Class.
2. Enumarations.
3. Struct.
4. Union.
5. Typedef.
ENUMARATIONS
You'll sometimes be faced with the need for variables that have a limited set of possible values
that can be usally referred to by name. For example, the state variable like
IDLE,READY,BUZY etx of state machine can only have the all the states defined and
Refraining or displaying these states using the state name will be more comfortable. There's a
specific facility, called an enumeration in SystemVerilog . Enumerated data types assign a
symbolic name to each legal value taken by the data type. Let's create an example using one of
the ideas I just mentioned-a state machine .
One more advantage of enumerated datatypes is that if you don't initialize then , each one would
have a unique value. By defaule they are int types. In the previous examples, IDLE is 0,
READY is 1 and BUZY is 2. These values can be printed as values or strings.
The values can be set for some of the names and not set for other names. A name without a value
is automatically assigned an increment of the value of the previous name. The value of first
name by default is 0.
// c is automatically assigned the increment-value of 8
enum {a=3, b=7, c} alphabet;
// Syntax error: c and d are both assigned 8
enum {a=0, b=7, c, d=8} alphabet;
// a=0, b=7, c=8
enum {a, b=7, c} alphabet;
Enumarated Methods:
SystemVerilog includes a set of specialized methods to enable iterating over the values of
enumerated.
The first() method returns the value of the first member of the enumeration.
The last() method returns the value of the last member of the enumeration.
The next() method returns the Nth next enumeration value (default is the next one) starting from
the current value of the given variable.
The prev() method returns the Nth previous enumeration value (default is the previous one)
starting from the current value of the given variable.
The name() method returns the string representation of the given enumeration value. If the given
value is not a member of the enumeration, the name() method returns the empty string.
EXAMPLE : ENUM methods
module enum_method;
typedef enum {red,blue,green} colour;
colour c;
initial
begin
c = c.first();
$display(" %s ",c.name);
c = c.next();
$display(" %s ",c.name);
c = c.last();
$display(" %s ",c.name);
c = c.prev();
$display(" %s ",c.name);
end
endmodule
RESULTS :
red
blue
green
blue
Enum Numerical Expressions
Elements of enumerated type variables can be used in numerical expressions. The value used in
the expression is the numerical value associated with the enumerated value.
An enum variable or identifier used as part of an expression is automatically cast to the base type
of the enum declaration (either explicitly or using int as the default). A cast shall be required for
an expression that is assigned to an enum variable where the type of the expression is not
equivalent to the enumeration type of the variable.
EXAMPLE:
module enum_method;
typedef enum {red,blue,green} colour;
colour c,d;
int i;
initial
begin
$display("%s",c.name());
d = c;
$display("%s",d.name());
d = colour'(c + 1); // use casting
$display("%s",d.name());
i = d; // automatic casting
$display("%0d",i);
c = colour'(i);
$display("%s",c.name());
end
endmodule
RESULT
red
red
blue
1
blue
TIP: If you want to use X or Z as enum values, then define it using 4-state data type explicitly.
enum integer {IDLE, XX='x, S1='b01, S2='b10} state, next;
my_data_struct.a = 123;
$display(" a value is %d ",my_data_struct.a);
union {
int a;
byte b;
bit [7:0] c;
} my_data;
memory allocation for the above defined struct "my_data_struct".
Memory allocation for the above defined union "my_data_union".
Packed Structures:
In verilog , it is not convenient for subdividing a vector into subfield. Accessing subfield requires
the index ranges.
For example
reg [0:47] my_data;
`define a_indx 16:47
`define b_indx 8:15
`define c_indx 0:7
my_data[`b_indx] = 8'b10; // writing to subfield b
$display(" %d ",my_data[`a_indx]); // reading subfield a
A packed structure is a mechanism for subdividing a vector into subfields that can be
conveniently accessed as members. Consequently, a packed structure consists of bit fields, which
are packed together in memory without gaps. A packed struct or union type must be declared
explicitly using keyword "packed".
struct packed {
integer a;
byte b;
bit [0:7] c;
} my_data;
my_data.b = 8'b10;
$display("%d", my_data.a);
Memory allocation for the above defined packed struct "my_data".
One or more bits of a packed structure can be selected as if it were a packed array, assuming an
[n-1:0] numbering:
My_data [15:8] // b
If all members of packed structure are 2-state, the structure as a whole is treated as a 2-state
vector.
If all members of packed structure is 4-state, the structure as a whole is treated as a 4-state
vector.
If there are also 2-state members, there is an implicit conversion from 4-state to 2-state when
reading those members, and from 2-state to 4-state when writing them.
TYPEDEF
A typedef declaration lets you define your own identifiers that can be used in place of type
specifiers such as int, byte, real. Let us see an example of creating data type "nibble".
typedef bit[3:0] nibble; // Defining nibble data type.
nibble a, b; // a and b are variables with nibble data types.
Advantages Of Using Typedef :
Shorter names are easier to type and reduce typing errors.
Improves readability by shortening complex declarations.
Improves understanding by clarifying the meaning of data.
Changing a data type in one place is easier than changing all of its uses throughout the code.
Allows defining new data types using structs, unions and Enumerations also.
Increases reusability.
Useful is type casting.
ARRAYS
Arrays hold a fixed number of equally-sized data elements. Individual elements are accessed by
index using a consecutive range of integers. Some type of arrays allows to access individual
elements using non consecutive values of any data types. Arrays can be classified as fixed-sized
arrays (sometimes known as static arrays) whose size cannot change once their declaration is
done, or dynamic arrays, which can be resized.
Fixed Arrays:
"Packed array" to refer to the dimensions declared before the object name and "unpacked array"
refers to the dimensions declared after the object name.
SystemVerilog accepts a single number, as an alternative to a range, to specify the size of an
unpacked array. That is, [size] becomes the same as [0:size-1].
// Packed Arrays
reg [0:10] vari; // packed array of 4-bits
wire [31:0] [1:0] vari; // 2-dimensional packed array
// Unpacked Arrays
wire status [31:0]; // 1 dimensional unpacked array
wire status [32]; // 1 dimensional unpacked array
In a list of multi dimensions, the rightmost one varies most rapidly than the left most
one. Packed dimension varies more rapidly than an unpacked.
In the following example, each dimension is having unique range and reading and writing to a
element shows exactly which index corresponds to which dimension.
module index();
bit [1:5][10:16] foo [21:27][31:38];
initial
begin
foo[24][34][4][14] = 1;
$display(" foo[24][34][4][14] is %d ",foo[24][34][4][14] );
end
endmodule
The result of reading from an array with an out of the address bounds or if any bit in the address
is X or Z shall return the default uninitialized value for the array element type.
As in Verilog, a comma-separated list of array declarations can be made. All arrays in the list
shall have the same data type and the same packed array dimensions.
module array();
bit [1:5][10:16] foo1 [21:27][31:38],foo2 [31:27][33:38];
initial
begin
$display(" dimensions of foo1 is %d foo2 is %d",$dimensions(foo1),$dimensions(foo2) );
$display(" reading with out of bound resulted %d",foo1[100][100][100][100]);
$display(" reading with index x resulted %d",foo1[33][1'bx]);
end
endmodule
RESULT:
RESULTS :
find()
find() returns all the elements satisfying the given expression
find_index()
find_index() returns the indexes of all the elements satisfying the given expression
find_first()
find_first() returns the first element satisfying the given expression
find_first_index()
find_first_index() returns the index of the first element satisfying the given expression
find_last()
find_last() returns the last element satisfying the given expression
find_last_index()
find_last_index() returns the index of the last element satisfying the given expression
For the following locator methods the "with" clause (and its expression) can be omitted if the
relational operators (<, >, ==) are defined for the element type of the given array. If a "with"
clause is specified, the relational operators (<, >, ==) must be defined for the type of the
expression.
min()
min() returns the element with the minimum value or whose expression evaluates to a minimum
max()
max() returns the element with the maximum value or whose expression evaluates to a maximum
unique()
unique() returns all elements with unique values or whose expression is unique
unique_index()
unique_index() returns the indexes of all elements with unique values or whose expression is
unique
EXAMPLE :
module arr_me;
string SA[10], qs[$];
int IA[*], qi[$];
initial
begin
SA[1:5] ={"Bob","Abc","Bob","Henry","John"};
IA[2]=3;
IA[3]=13;
IA[5]=43;
IA[8]=36;
IA[55]=237;
IA[28]=39;
// Find all items greater than 5
qi = IA.find( x ) with ( x > 5 );
for ( int j = 0; j < qi.size; j++ ) $write("%0d_",qi[j] );
$display("");
// Find indexes of all items equal to 3
qi = IA.find_index with ( item == 3 );
for ( int j = 0; j < qi.size; j++ ) $write("%0d_",qi[j] );
$display("");
// Find first item equal to Bob
qs = SA.find_first with ( item == "Bob" );
for ( int j = 0; j < qs.size; j++ ) $write("%s_",qs[j] );
$display("");
// Find last item equal to Henry
qs = SA.find_last( y ) with ( y == "Henry" );
for ( int j = 0; j < qs.size; j++ ) $write("%s_",qs[j] );
$display("");
// Find index of last item greater than Z
qi = SA.find_last_index( s ) with ( s > "Z" );
for ( int j = 0; j < qi.size; j++ ) $write("%0d_",qi[j] );
$display("");
// Find smallest item
qi = IA.min;
for ( int j = 0; j < qi.size; j++ ) $write("%0d_",qi[j] );
$display("");
// Find string with largest numerical value
qs = SA.max with ( item.atoi );
for ( int j = 0; j < qs.size; j++ ) $write("%s_",qs[j] );
$display("");
// Find all unique strings elements
qs = SA.unique;
for ( int j = 0; j < qs.size; j++ ) $write("%s_",qs[j] );
$display("");
// Find all unique strings in lowercase
qs = SA.unique( s ) with ( s.tolower );
for ( int j = 0; j < qs.size; j++ ) $write("%s_",qs[j] );
end
endmodule
RESULTS :
13_43_36_39_237_
2_
Bob_
Henry_
3_
_
_Bob_Abc_Henry_John_
_Bob_Abc_Henry_John_
Array Ordering Methods:
Array ordering methods can reorder the elements of one-dimensional arrays or queues. The
following ordering methods are supported:
reverse()
reverse() reverses all the elements of the packed or unpacked arrays.
sort()
sort() sorts the unpacked array in ascending order, optionally using the expression in the with
clause.
rsort()
rsort() sorts the unpacked array in descending order, optionally using the with clause expression.
shuffle()
shuffle() randomizes the order of the elements in the array.
EXAMPLE:
module arr_order; string s[] = '{ "one", "two", "three" };
initial
begin
s.reverse;
for ( int j = 0; j < 3;j++ ) $write("%s",s[j] );
s.sort;
for ( int j = 0; j < 3;j++ ) $write("%s",s[j] );
s.rsort;
for ( int j = 0; j < 3;j++ ) $write("%s",s[j] );
s.shuffle;
for ( int j = 0; j < 3;j++ ) $write("%s",s[j] );
end
endmodule
RESULT:
three two one
DYNAMIC ARRAYS
Verilog does not allow changing the dimensions of the array once it is declared. Most of the
time in verification, we need arrays whose size varies based on the some behavior. For example
Ethernet packet varies length from one packet to other packet. In verilog, for creating Ethernet
packet, array with Maximum packet size is declared and only the number of elements which are
require for small packets are used and unused elements are waste of memory.
To overcome this deficiency, System Verilog provides Dynamic Array. A dynamic array is
unpacked array whose size can be set or changed at runtime unlike verilog which needs size at
compile time. Dynamic arrays allocate storage for elements at run time along with the option of
changing the size.
Declaration Of Dynmic Array:
integer dyna_arr_1[],dyn_arr_2[],multi_dime_dyn_arr[][];
Allocating Elements:
New[]:The built-in function new allocates the storage and initializes the newly allocated array
elements either to their default initial value.
4
8
0
The information about the size of the dynamic array is with the array itself. It can be obtained
using .size() method. This will be very helpful when you are playing with array. You don't need
to pass the size information explicitly. We can also use system task $size() method instead of
.size() method. SystemVerilog also provides delete() method clears all the elements yielding an
empty array (zero size).
ASSOCIATIVE ARRAYS
Dynamic arrays are useful for dealing with contiguous collections of variables whose number
changes dynamically. Associative arrays give you another way to store information. When the
size of the collection is unknown or the data space is sparse, an associative array is a better
option. In Associative arrays Elements Not Allocated until Used. Index Can Be of Any Packed
Type, String or Class. Associative elements are stored in an order that ensures fastest access.
In an associative array a key is associated with a value. If you wanted to store the information of
various transactions in an array, a numerically indexed array would not be the best choice.
Instead, we could use the transaction names as the keys in associative array, and the value would
be their respective information. Using associative arrays, you can call the array element you need
using a string rather than a number, which is often easier to remember.
The syntax to declare an associative array is:
Elements in associative array elements can be accessed like those of one dimensional arrays.
Associative array literals use the '{index:value} syntax with an optional default index.
//associative array of 4-state integers indexed by strings, default is '1.
integer tab [string] = '{"Peter":20, "Paul":22, "Mary":23, default:-1 };
Associative Array Methods
SystemVerilog provides several methods which allow analyzing and manipulating associative
arrays. They are:
The num() or size() method returns the number of entries in the associative array.
The delete() method removes the entry at the specified index.
The exists() function checks whether an element exists at the specified index within the given
array.
The first() method assigns to the given index variable the value of the first (smallest) index in the
associative array. It returns 0 if the array is empty; otherwise, it returns 1.
The last() method assigns to the given index variable the value of the last (largest) index in the
associative array. It returns 0 if the array is empty; otherwise, it returns 1.
The next() method finds the entry whose index is greater than the given index. If there is a next
entry, the index variable is assigned the index of the next entry, and the function returns 1.
Otherwise, the index is unchanged, and the function returns 0.
The prev() function finds the entry whose index is smaller than the given index. If there is a
previous entry, the index variable is assigned the index of the previous entry, and the function
returns 1. Otherwise, the index is unchanged, and the function returns 0.
EXAMPLE
module assoc_arr;
int temp,imem[*];
initial
begin
imem[ 2'd3 ] = 1;
imem[ 16'hffff ] = 2;
imem[ 4'b1000 ] = 3;
$display( "%0d entries", imem.num );
if(imem.exists( 4'b1000) )
$display("imem.exists( 4b'1000) ");
imem.delete(4'b1000);
if(imem.exists( 4'b1000) )
$display(" imem.exists( 4b'1000) ");
else
$display(" ( 4b'1000) not existing");
if(imem.first(temp))
$display(" First entry is at index %0db ",temp);
if(imem.next(temp))
$display(" Next entry is at index %0h after the index 3",temp);
// To print all the elements alone with its indexs
if (imem.first(temp) )
do
$display( "%d : %d", temp, imem[temp] );
while ( imem.next(temp) );
end
endmodule
RESULT
3 entries
imem.exists( 4b'1000)
( 4b'1000) not existing
First entry is at index 3b
Next entry is at index ffff after the index 3
3: 1
65535 : 2
QUEUES
A queue is a variable-size, ordered collection of homogeneous elements. A Queue is analogous
to one dimensional unpacked array that grows and shrinks automatically. Queues can be used to
model a last in, first out buffer or first in, first out buffer. Queues support insertion and deletion
of elements from random locations using an index. Queues Can be passed to tasks / functions as
ref or non-ref arguments. Type checking is also done.
Queue Operators:
Queues and dynamic arrays have the same assignment and argument passing semantics. Also,
queues support the same operations that can be performed on unpacked arrays and use the same
operators and rules except as defined below:
int q[$] = { 2, 4, 8 };
int p[$];
int e, pos;
e = q[0]; // read the first (leftmost) item
e = q[$]; // read the last (rightmost) item
q[0] = e; // write the first item
p = q; // read and write entire queue (copy)
q = { q, 6 }; // insert '6' at the end (append 6)
q = { e, q }; // insert 'e' at the beginning (prepend e)
q = q[1:$]; // delete the first (leftmost) item
q = q[0:$-1]; // delete the last (rightmost) item
q = q[1:$-1]; // delete the first and last items
q = {}; // clear the queue (delete all items)
q = { q[0:pos-1], e, q[pos,$] }; // insert 'e' at position pos
q = { q[0:pos], e, q[pos+1,$] }; // insert 'e' after position pos
Queue Methods:
In addition to the array operators, queues provide several built-in methods. They are:
The size() method returns the number of items in the queue. If the queue is empty, it returns 0.
The insert() method inserts the given item at the specified index position.
The delete() method deletes the item at the specified index.
The pop_front() method removes and returns the first element of the queue.
The pop_back() method removes and returns the last element of the queue.
The push_front() method inserts the given element at the front of the queue.
The push_back() method inserts the given element at the end of the queue.
EXAMPLE
module queues;
byte qu [$] ;
initial
begin
qu.push_front(2);
qu.push_front(12);
qu.push_front(22);
qu.push_back(11);
qu.push_back(99);
$display(" %d ",qu.size() );
$display(" %d ",qu.pop_front() );
$display(" %d ",qu.pop_back() );
qu.delete(3);
$display(" %d ",qu.size() );
end
endmodule
RESULTS :
5
22
99
Dynamic Array Of Queues Queues Of Queues
EXAMPLE:
module top;
typedef int qint_t[$];
// dynamic array of queues
qint_t DAq[]; // same as int DAq[][$];
// queue of queues
qint_t Qq[$]; // same as int Qq[$][$];
// associative array of queues
qint_t AAq[string]; // same as int AAq[string][$];
initial begin
// Dynamic array of 4 queues
DAq = new[4];
// Push something onto one of the queues
DAq[2].push_back(1);
// initialize another queue with three entries
DAq[0] = {1,2,3};
$display("%p",DAq);
// Queue of queues -two
Qq= {{1,2},{3,4,5}};
Qq.push_back(qint_t'{6,7});
Qq[2].push_back(1);
$display("%p",Qq);
// Associative array of queues
AAq["one"] = {};
AAq["two"] = {1,2,3,4};
AAq["one"].push_back(5);
$display("%p",AAq);
end
endmodule : top
RESULTS:
'{'{1, 2, 3}, '{}, '{1}, '{}}
'{'{1, 2}, '{3, 4, 5}, '{6, 7, 1}}
'{one:'{5}, two:'{1, 2, 3, 4} }
COMPARISON OF ARRAYS
Static Array
Size should be known at compilation time.
Time require to access any element is less.
if not all elements used by the application, then memory is wasted.
Not good for sparse memory or when the size changes.
Good for contagious data.
Associativearray
No need of size information at compile time.
Time require to access an element increases with size of the array.
Compact memory usage for sparse arrays.
User don't need to keep track of size. It is automatically resized.
Good inbuilt methods for Manipulating and analyzing the content.
Dynamicarray
No need of size information at compile time.
To set the size or resize, the size should be provided at runtime.
Performance to access elements is same as Static arrays.
Good for contagious data.
Memory usage is very good, as the size can be changed dynamically.
Queues
No need of size information at compile time.
Performance to access elements is same as Static arrays.
User doesn't need to provide size information to change the size. It is automatically resized.
Rich set of inbuilt methods for Manipulating and analyzing the content.
Useful in self-checking modules. Very easy to work with out of order transactions.
Inbuilt methods for sum of elements, sorting all the elements.
Searching for elements is very easy even with complex expressions.
Useful to model FIFO or LIFO.
LINKED LIST
The List package implements a classic list data-structure, and is analogous to the STL (Standard
Template Library) List container that is popular with C++ programmers. The container is defined
as a parameterized class, meaning that it can be customized to hold data of any type. The List
package supports lists of any arbitrary predefined type, such as integer, string, or class object.
First declare the Linked list type and then take instances of it. SystemVerilog has many methods
to operate on these instances.
A double linked list is a chain of data structures called nodes. Each node has 3 members, one
points to the next item or points to a null value if it is last node, one points to the previous item
or points to a null value if it is first node and other has the data.
The disadvantage of the linked list is that data can only be accessed sequentially and not in
random order. To read the 1000th element of a linked list, you must read the 999 elements that
precede it.
List Definitions:
list :- A list is a doubly linked list, where every element has a predecessor and successor. It is a
sequence that supports both forward and backward traversal, as well as amortized constant time
insertion and removal of elements at the beginning, end, or middle.
container :- A container is a collection of objects of the same type .Containers are objects that
contain and manage other objects and provide iterators that allow the contained objects
(elements) to be addressed. A container has methods for accessing its elements. Every container
has an associated iterator type that can be used to iterate through the container´s elements.
iterator :- Iterators provide the interface to containers. They also provide a means to traverse the
container elements. Iterators are pointers to nodes within a list. If an iterator points to an object
in a range of objects and the iterator is incremented, the iterator then points to the next object in
the range.
Procedure To Create And Use List:
1. include the generic List class declaration
`include <List.vh>
2. Declare list variable
List#(integer) il; // Object il is a list of integer
3. Declaring list iterator
List_Iterator#(integer) itor; //Object s is a list-of-integer iterator
List_iterator Methods
The List_Iterator class provides methods to iterate over the elements of lists.
The next() method changes the iterator so that it refers to the next element in the list.
The prev() method changes the iterator so that it refers to the previous element in the list.
The eq() method compares two iterators and returns 1 if both iterators refer to the same list
element.
The neq() method is the negation of eq().
The data() method returns the data stored in the element at the given iterator location.
List Methods
The List class provides methods to query the size of the list; obtain iterators to the head or tail of
the list;
retrieve the data stored in the list; and methods to add, remove, and reorder the elements of the
list.
The size() method returns the number of elements stored in the list.
The empty() method returns 1 if the number elements stored in the list is zero and 0 otherwise.
The push_front() method inserts the specified value at the front of the list.
The push_back() method inserts the specified value at the end of the list.
The front() method returns the data stored in the first element of the list.
The back() method returns the data stored in the last element of the list.
The pop_front() method removes the first element of the list.
The pop_back() method removes the last element of the list.
The start() method returns an iterator to the position of the first element in the list.
The finish() method returns an iterator to a position just past the last element in the list.
The insert() method inserts the given data into the list at the position specified by the iterator.
The insert_range() method inserts the elements contained in the list range specified by the
iterators first and last at the specified list position.
The erase() method removes from the list the element at the specified position.
The erase_range() method removes from a list the range of elements specified by the first and
last iterators.
The set() method assigns to the list object the elements that lie in the range specified by the first
and last iterators.
The swap() method exchanges the contents of two equal-size lists.
The clear() method removes all the elements from a list, but not the list itself.
The purge() method removes all the list elements (as in clear) and the list itself.
EXAMPLE
module lists();
List#(integer) List1;
List_Iterator#(integer) itor;
initial begin
List1 = new();
$display (" size of list is %d \n",List1.size());
List1.push_back(10);
List1.push_front(22);
$display (" size of list is %d \n",List1.size());
$display (" poping from list : %d \n",List1.front());
$display (" poping from list : %d \n",List1.front());
List1.pop_front();
List1.pop_front();
$display (" size of list is %d \n",List1.size());
List1.push_back(5);
List1.push_back(55);
List1.push_back(555);
List1.push_back(5555);
$display (" size of list is %d \n",List1.size());
itor = List1.start();
$display (" startn of list %d \n",itor.data());
itor.next();
$display (" second element of list is %d \n",itor.data());
itor.next();
$display (" third element of list is %d \n",itor.data());
itor.next();
$display (" fourth element of list is %d \n",itor.data());
itor = List1.erase(itor);
$display (" after erasing element,the itor element of list is %d \n",itor.data());
itor.prev();
$display(" prevoious element is %d \n",itor.data());
end
endmodule
RESULT:
size of list is 0
size of list is 2
poping from list : 22
poping from list : 22
size of list is 0
size of list is 4
startn of list 5
second element of list is 55
third element of list is 555
fourth element of list is 5555
after erasing element,the itor element of list is x
prevoious element is 555
CASTING
Verilog is loosely typed . Assignments can be done from one data type to other data types based
on predefined rules.The compiler only checks that the destination variable and source expression
are scalars. Otherwise, no type checking is done at compile time. Systemverilog has complex
data types than Verilog. It's necessary for SystemVerilog to be much stricter about type
conversions than Verilog, so Systemverilog provided the cast(`) operator, which specifies the
type to use for a specific expression. Using Casting one can assign values to variables that might
not ordinarily be valid because of differing data type. SystemVerilog adds 2 types of casting.
Static casting and dynamic casting.
Static Casting
A data type can be changed by using a cast ( ' ) operation. In a static cast, the expression to be
cast shall be enclosed in parentheses that are prefixed with the casting type and an apostrophe. If
the expression is assignment compatible with the casting type, then the cast shall return the value
that a variable of the casting type would hold after being assigned the expression.
EXAMPLE:
int'(2.0 * 3.0)
shortint'{{8'hFA,8'hCE}}
signed'(x)
17'(x - 2)
Dynamic Casting
SystemVerilog provides the $cast system task to assign values to variables that might not
ordinarily be valid because of differing data type. $cast can be called as either a task or a
function.
The syntax for $cast is as follows:
function int $cast( singular dest_var, singular source_exp );
or
task $cast( singular dest_var, singular source_exp );
The dest_var is the variable to which the assignment is made. The source_exp is the expression
that is to be assigned to the destination variable. Use of $cast as either a task or a function
determines how invalid assignments are handled. When called as a task, $cast attempts to assign
the source expression to the destination variable. If the assignment is invalid, a run-time error
occurs, and the destination variable is left unchanged.
EXAMPLE:
typedef enum { red, green, blue, yellow, white, black } Colors;
Colors col;
$cast( col, 2 + 3 );
Cast Errors
Following example shows the compilation error.
EXAMPLE:
module enum_method;
typedef enum {red,blue,green} colour;
colour c,d;
int i;
initial
begin
d = (c + 1);
end
endmodule
RESULT
Illegal assignment
Following example shows the simulation error. This is compilation error free. In this example , d
is assigned c + 10 , which is out of bound in enum colour.
EXAMPLE:
module enum_method;
typedef enum {red,blue,green} colour;
colour c,d;
int i;
initial
begin
$cast(d,c + 10);
end
endmodule
RESULT
Dynamic cast failed
DATA DECLARATION
Scope And Lifetime:
Global :
SystemVerilog adds the concept of global scope. Any declarations and definitions which is
declared outside a module, interface, task, or function, is global in scope. Global variables have a
static lifetime (exists for the whole elaboration and simulation time). Datatypes, tasks,functions,
class definitions can be in global scope. Global members can be referenced explicitly via the
$root . All these can be accessed from any scope as this is the highest scope and any other scope
will be below the global.
Local :
Local declarations and definitions are accessible at the scope where they are defined and below.
By default they are static in life time. They can be made to automatic. To access these local
variables which are static, hierarchical pathname should be used.
int st0; //Static. Global Variable. Declared outside module
task disp(); //Static. Global Task.
module msl;
int st0; //static. Local to module
initial begin
int st1; //static. Local to module
static int st2; //static. Local to Module
automatic int auto1; //automatic.
end
task automatic t1(); //Local task definition.
int auto2; //automatic. Local to task
static int st3; //static.Local to task. Hierarchical path access allowed
automatic int auto3; //automatic. Local to task
$root.st0 = st0; //$root.sto is global variable, st0 is local to module.
endtask
endmodule
Alias:
The Verilog assign statement is a unidirectional assignment. To model a bidirectional short-
circuit connection it is necessary to use the alias statement.
This example strips out the least and most significant bytes from a four byte bus:
module byte_rip (inout wire [31:0] W, inout wire [7:0] LSB, MSB);
alias W[7:0] = LSB;
alias W[31:24] = MSB;
endmodule
For example:
logic abc;
The following statements are legal assignments to logic abc:
OPERATORS 1
The SystemVerilog operators are a combination of Verilog and C operators. In both languages,
the type and size of the operands is fixed, and hence the operator is of a fixed type and size. The
fixed type and size of operators is preserved in SystemVerilog. This allows efficient code
generation.
Verilog does not have assignment operators or increment and decrement operators.
SystemVerilog includes the C assignment operators, such as +=, and the C increment and
decrement operators, ++ and --.
Verilog-2001 added signed nets and reg variables, and signed based literals. There is a difference
in the rules for combining signed and unsigned integers between Verilog and C. SystemVerilog
uses the Verilog rules.
Operators In Systemverilog
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, <<<=,>>>=.
An assignment operator is semantically equivalent to a blocking assignment, with the exception
that any left-hand side index expression is only evaluated once.
For example:
a[i]+=2; // same as a[i] = a[i] +2;
Following are the new SystemVerilog assignment operators and its equivalent in verilog
Assignments In Expression
In SystemVerilog, an expression can include a blocking assignment. such an assignment must be
enclosed in parentheses to avoid common mistakes such as using a=b for a==b, or a|=b for a!=b.
if ((a=b)) b = (a+=1); // assign b to a
a = (b = (c = 5));// assign 5 to c
if(a=b) // error in systemverilog
(a=b) statement assigns b value to a and then returns a value.
if((a=b)) is equivalent to
a=b;
if(a)
EXAMPLE
module assignment();
int a,b,c;
initial begin
a = 1; b =2;c =3;
if((a=b))
$display(" a value is %d ",a);
a = (b = (c = 5));
$display(" a is %d b is %d c is %d ",a,b,c);
end
endmodule
RESULT
a value is 2
a is 5 b is 5 c is 5
Concatenation :
{} concatenation right of assignment.
´{} concatenation left of assignment.
EXAMPLE :Concatenation
program main ;
bit [4:0] a;
reg b,c,d;
initial begin
b = 0;
c = 1;
d = 1;
a = {b,c,0,0,d};
{b,c,d} = 3'b111;
$display(" a %b b %b c %b d %b ",a,b,c,d);
end
endprogram
RESULTS
a 00001 b 1 c 1 d 1
Arithmetic:
EXAMPLE :Arithmetic
program main;
integer a,b;
initial
begin
b = 10;
a = 22;
$display(" -(nagetion) is %0d ",-(a) );
$display(" a + b is %0d ",a+b);
$display(" a - b is %0d ",a-b);
$display(" a * b is %0d ",a*b);
$display(" a / b is %0d ",a/b);
$display(" a modulus b is %0d ",a%b);
end
endprogram
RESULTS
-(nagetion) is -22
a + b is 32
a - b is 12
a * b is 220
a / b is 2
a modules b is 2
Following tabel shows the opwer operator rules for calculating the result.
program main;
integer op1_neg,op1_n1,op1_0,op1_p1,op1_pos;
integer op2_pos_odd,op2_pos_even,op2_zero,op2_neg_odd,op2_neg_even;
initial
begin
op1_neg = -10;op1_n1 = -1;op1_0 = 0;op1_p1 = 1;op1_pos = 10;
op2_pos_odd = 9;op2_pos_even =10;op2_zero=0;op2_neg_odd =-9;op2_neg_even=-10;
$display(" | -10 -1 0 1 10");
$display("---|--------------------------------------------------------");
$display(" 9| %d %d %d %d
%d",op1_neg**op2_pos_odd,op1_n1**op2_pos_odd,op1_0**op2_pos_odd,op1_p1**op2_pos_
odd,op1_pos**op2_pos_odd);
$display(" 10| %d %d %d %d
%d",op1_neg**op2_pos_even,op1_n1**op2_pos_even,op1_0**op2_pos_even,op1_p1**op2_p
os_even,op1_pos**op2_pos_even);
$display(" 0| %d %d %d %d
%d",op1_neg**op2_zero,op1_n1**op2_zero,op1_0**op2_zero,op1_p1**op2_zero,op1_pos**o
p2_zero);
$display(" -9| %d %d %d %d
%d",op1_neg**op2_neg_odd,op1_n1**op2_neg_odd,op1_0**op2_neg_odd,op1_p1**op2_neg_
odd,op1_pos**op2_neg_odd);
$display("-10| %d %d %d %d
%d",op1_neg**op2_neg_even,op1_n1**op2_neg_even,op1_0**op2_neg_even,op1_p1**op2_n
eg_even,op1_pos**op2_neg_even);
end
endprogram
RESULT
| -10 -1 0 1 10
---|--------------------------------------------------------
9| 3294967296 4294967295 0 1 1000000000
10| 1410065408 1 0 1 1410065408
0| 1 1 1 1 1
-9| 0 4294967295 x 1 0
-10| 0 1 x 1 0
Relational:
# > >= < <= relational
EXAMPLE :Relational
program main ;
integer a,b;
initial
begin
b = 10;
a = 22;
$display(" a < b is %0d \n",a < b);
$display(" a > b is %0d \n",a >b);
$display(" a <= b is %0d \n",a <= b);
$display(" a >= b is %0d \n",a >= b);
end
endprogram
RESULTS
a < b is 0
a > b is 1
a <= b is 0
a >= b is 1
Equality:
The different types of equality (and inequality) operators in SystemVerilog behave differently
when their operands contain unknown values (X or Z). The == and != operators may result in X
if any of their operands contains an X or Z. The === and !== check the 4-state explicitly,
therefore, X and Z values shall either match or mismatch, never resulting in X. The ==? and !=?
operators may result in X if the left operand contains an X or Z that is not being compared with a
wildcard in the right operand.
EXAMPLE : logical Equality
program main;
reg[3:0] a;
reg[7:0] x, y, z;
initial begin
a = 4'b0101;
x = 8'b1000_0101;
y = 8'b0000_0101;
z = 8'b0xx0_0101;
if (x == a)
$display("x equals a is TRUE.\n");
if (y == a)
$display("y equals a is TRUE.\n");
if (z == a)
$display("z equals a is TRUE.\n");
end
endprogram
RESULTS:
y equals a is TRUE.
EXAMPLE:case equality:
program main ;
reg a_1,a_0,a_x,a_z;
reg b_1,b_0,b_x,b_z;
initial
begin
a_1 = 'b1;a_0 = 'b0;a_x = 'bx;a_z = 'bz;
b_1 = 'b1;b_0 = 'b0;b_x = 'bx;b_z = 'bz;
$display("--------------------------");
$display (" == 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 == b_0,a_0 == b_1,a_0 == b_x,a_0 == b_z);
$display (" 1 %b %b %b %b ",a_1 == b_0,a_1 == b_1,a_1 == b_x,a_1 == b_z);
$display (" x %b %b %b %b ",a_x == b_0,a_x == b_1,a_x == b_x,a_x == b_z);
$display (" z %b %b %b %b ",a_z == b_0,a_z == b_1,a_z == b_x,a_z == b_z);
$display("--------------------------");
$display("--------------------------");
$display (" === 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 === b_0,a_0 === b_1,a_0 === b_x,a_0 === b_z);
$display (" 1 %b %b %b %b ",a_1 === b_0,a_1 === b_1,a_1 === b_x,a_1 === b_z);
$display (" x %b %b %b %b ",a_x === b_0,a_x === b_1,a_x === b_x,a_x === b_z);
$display (" z %b %b %b %b ",a_z === b_0,a_z === b_1,a_z === b_x,a_z === b_z);
$display("--------------------------");
$display("--------------------------");
$display (" =?= 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 =?= b_0,a_0 =?= b_1,a_0 =?= b_x,a_0 =?= b_z);
$display (" 1 %b %b %b %b ",a_1 =?= b_0,a_1 =?= b_1,a_1 =?= b_x,a_1 =?= b_z);
$display (" x %b %b %b %b ",a_x =?= b_0,a_x =?= b_1,a_x =?= b_x,a_x =?= b_z);
$display (" z %b %b %b %b ",a_z =?= b_0,a_z =?= b_1,a_z =?= b_x,a_z =?= b_z);
$display("--------------------------");
$display("--------------------------");
$display (" != 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 != b_0,a_0 != b_1,a_0 != b_x,a_0 != b_z);
$display (" 1 %b %b %b %b ",a_1 != b_0,a_1 != b_1,a_1 != b_x,a_1 != b_z);
$display (" x %b %b %b %b ",a_x != b_0,a_x != b_1,a_x != b_x,a_x != b_z);
$display (" z %b %b %b %b ",a_z != b_0,a_z != b_1,a_z != b_x,a_z != b_z);
$display("--------------------------");
$display("--------------------------");
$display (" !== 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 !== b_0,a_0 !== b_1,a_0 !== b_x,a_0 !== b_z);
$display (" 1 %b %b %b %b ",a_1 !== b_0,a_1 !== b_1,a_1 !== b_x,a_1 !== b_z);
$display (" x %b %b %b %b ",a_x !== b_0,a_x !== b_1,a_x !== b_x,a_x !== b_z);
$display (" z %b %b %b %b ",a_z !== b_0,a_z !== b_1,a_z !== b_x,a_z !== b_z);
$display("--------------------------");
$display("--------------------------");
$display (" !?= 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 !?= b_0,a_0 !?= b_1,a_0 !?= b_x,a_0 !?= b_z);
$display (" 1 %b %b %b %b ",a_1 !?= b_0,a_1 !?= b_1,a_1 !?= b_x,a_1 !?= b_z);
$display (" x %b %b %b %b ",a_x !?= b_0,a_x !?= b_1,a_x !?= b_x,a_x !?= b_z);
$display (" z %b %b %b %b ",a_z !?= b_0,a_z !?= b_1,a_z !?= b_x,a_z !?= b_z);
$display("--------------------------");
end
endprogram
RESULTS
--------------------------
== 0 1 x z
--------------------------
0 1 0 x x
1 0 1 x x
x x x x x
z x x x x
--------------------------
--------------------------
=== 0 1 x z
--------------------------
0 1 0 0 0
1 0 1 0 0
x 0 0 1 0
z 0 0 0 1
--------------------------
--------------------------
=?= 0 1 x z
--------------------------
0 1 0 1 1
1 0 1 1 1
x 1 1 1 1
z 1 1 1 1
--------------------------
--------------------------
!= 0 1 x z
--------------------------
0 0 1 x x
1 1 0 x x
x x x x x
z x x x x
--------------------------
--------------------------
!== 0 1 x z
--------------------------
0 0 1 1 1
1 1 0 1 1
x 1 1 0 1
z 1 1 1 0
--------------------------
--------------------------
!?= 0 1 x z
--------------------------
0 0 1 0 0
1 1 0 0 0
x 0 0 0 0
z 0 0 0 0
--------------------------
OPERATORS 2
Logical :
SystemVerilog added two new logical operators logical implication (->), and logical equivalence
(<->). The logical implication expression1 -> expression2 is logically equivalent to (!expression1
|| expression2), and the logical equivalence expression1 <-> expression2 is logically equivalent
to ((expression1 -> expression2) && (expression2 -> expression1)).
EXAMPLE : Logical
program main ;
reg a_1,a_0,a_x,a_z;
reg b_1,b_0,b_x,b_z;
initial begin
a_1 = 'b1;a_0 = 'b0;a_x = 'bx;a_z = 'bz;
b_1 = 'b1;b_0 = 'b0;b_x = 'bx;b_z = 'bz;
$display("--------------------------");
$display (" && 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 && b_0,a_0 && b_1,a_0 && b_x,a_0&& b_z);
$display (" 1 %b %b %b %b ",a_1 && b_0,a_1 && b_1,a_1 && b_x,a_1&& b_z);
$display (" x %b %b %b %b ",a_x && b_0,a_x && b_1,a_x && b_x,a_x&& b_z);
$display (" z %b %b %b %b ",a_z && b_0,a_z && b_1,a_z && b_x,a_z&& b_z);
$display("--------------------------");
$display("--------------------------");
$display (" || 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 || b_0,a_0 || b_1,a_0 || b_x,a_0 || b_z);
$display (" 1 %b %b %b %b ",a_1 || b_0,a_1 || b_1,a_1 || b_x,a_1 || b_z);
$display (" x %b %b %b %b ",a_x || b_0,a_x || b_1,a_x || b_x,a_x || b_z);
$display (" z %b %b %b %b ",a_z || b_0,a_z || b_1,a_z || b_x,a_z || b_z);
$display("--------------------------");
$display("--------------------------");
$display (" ! 0 1 x z ");
$display("--------------------------");
$display (" %b %b %b %b ",!b_0,!b_1,!b_x,!b_z);
$display("--------------------------");
end
endprogram
RESULTS
--------------------------
&& 0 1 x z
--------------------------
0 0 0 0 0
1 0 1 x x
x 0 x x x
z 0 x x x
--------------------------
--------------------------
|| 0 1 x z
--------------------------
0 0 1 x x
1 1 1 1 1
x x 1 x x
z x 1 x x
--------------------------
--------------------------
! 0 1 x z
--------------------------
1 0 x x
--------------------------
Bitwise :
In Systemverilog, bitwise exclusive nor has two notations (~^ and ^~).
EXAMPLE : Bitwise
program main ;
reg a_1,a_0,a_x,a_z;
reg b_1,b_0,b_x,b_z;
initial begin
a_1 = 'b1;a_0 = 'b0;a_x = 'bx;a_z = 'bz;
b_1 = 'b1;b_0 = 'b0;b_x = 'bx;b_z = 'bz;
$display("--------------------------");
$display (" ~ 0 1 x z ");
$display("--------------------------");
$display (" %b %b %b %b ",~b_0,~b_1,~b_x,~b_z);
$display("--------------------------");
$display("--------------------------");
$display (" & 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 & b_0,a_0 & b_1,a_0 & b_x,a_0 & b_z);
$display (" 1 %b %b %b %b ",a_1 & b_0,a_1 & b_1,a_1 & b_x,a_1 & b_z);
$display (" x %b %b %b %b ",a_x & b_0,a_x & b_1,a_x & b_x,a_x & b_z);
$display (" z %b %b %b %b ",a_z & b_0,a_z & b_1,a_z & b_x,a_z & b_z);
$display("--------------------------");
$display("--------------------------");
$display (" &~ 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 &~ b_0,a_0 &~ b_1,a_0 &~ b_x,a_0 &~ b_z);
$display (" 1 %b %b %b %b ",a_1 &~ b_0,a_1 &~ b_1,a_1 &~ b_x,a_1 &~ b_z);
$display (" x %b %b %b %b ",a_x &~ b_0,a_x &~ b_1,a_x &~ b_x,a_x &~ b_z);
$display (" z %b %b %b %b ",a_z &~ b_0,a_z &~ b_1,a_z &~ b_x,a_z &~ b_z);
$display("--------------------------");
$display("--------------------------");
$display (" | 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 | b_0,a_0 | b_1,a_0 | b_x,a_0 | b_z);
$display (" 1 %b %b %b %b ",a_1 | b_0,a_1 | b_1,a_1 | b_x,a_1 | b_z);
$display (" x %b %b %b %b ",a_x | b_0,a_x | b_1,a_x | b_x,a_x | b_z);
$display (" z %b %b %b %b ",a_z | b_0,a_z | b_1,a_z | b_x,a_z | b_z);
$display("--------------------------");
$display (" |~ 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 |~ b_0,a_0 |~ b_1,a_0 |~ b_x,a_0 |~ b_z);
$display (" 1 %b %b %b %b ",a_1 |~ b_0,a_1 |~ b_1,a_1 |~ b_x,a_1 |~ b_z);
$display (" x %b %b %b %b ",a_x |~ b_0,a_x |~ b_1,a_x |~ b_x,a_x |~ b_z);
$display (" z %b %b %b %b ",a_z |~ b_0,a_z |~ b_1,a_z |~ b_x,a_z |~ b_z);
$display("--------------------------");
$display("--------------------------");
$display (" ^ 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 ^ b_0,a_0 ^ b_1,a_0 ^ b_x,a_0 ^ b_z);
$display (" 1 %b %b %b %b ",a_1 ^ b_0,a_1 ^ b_1,a_1 ^ b_x,a_1 ^ b_z);
$display (" x %b %b %b %b ",a_x ^ b_0,a_x ^ b_1,a_x ^ b_x,a_x ^ b_z);
$display (" z %b %b %b %b ",a_z ^ b_0,a_z ^ b_1,a_z ^ b_x,a_z ^ b_z);
$display("--------------------------");
$display (" ^~ 0 1 x z ");
$display("--------------------------");
$display (" 0 %b %b %b %b ",a_0 ^~ b_0,a_0 ^~ b_1,a_0 ^~ b_x,a_0 ^~ b_z);
$display (" 1 %b %b %b %b ",a_1 ^~ b_0,a_1 ^~ b_1,a_1 ^~ b_x,a_1 ^~ b_z);
$display (" x %b %b %b %b ",a_x ^~ b_0,a_x ^~ b_1,a_x ^~ b_x,a_x ^~ b_z);
$display (" z %b %b %b %b ",a_z ^~ b_0,a_z ^~ b_1,a_z ^~ b_x,a_z ^~ b_z);
$display("--------------------------");
end
endprogram
RESULTS
--------------------------
~ 0 1 x z
--------------------------
1 0 x x
--------------------------
--------------------------
& 0 1 x z
--------------------------
0 0 0 0 0
1 0 1 x x
x 0 x x x
z 0 x x x
--------------------------
--------------------------
&~ 0 1 x z
--------------------------
0 0 0 0 0
1 1 0 x x
x x 0 x x
z x 0 x x
--------------------------
--------------------------
| 0 1 x z
--------------------------
0 0 1 x x
1 1 1 1 1
x x 1 x x
z x 1 x x
--------------------------
|~ 0 1 x z
--------------------------
0 1 0 x x
1 1 1 1 1
x 1 x x x
z 1 x x x
--------------------------
--------------------------
^ 0 1 x z
--------------------------
0 0 1 x x
1 1 0 x x
x x x x x
z x x x x
--------------------------
^~ 0 1 x z
--------------------------
0 1 0 x x
1 0 1 x x
x x x x x
z x x x x
--------------------------
Reduction :
EXAMPLE : Reduction
program main ;
reg [3:0] a_1,a_0,a_01xz,a_1xz,a_0xz,a_0dd1,a_even1;
initial
begin
a_1 = 4'b1111 ;
a_0 = 4'b0000 ;
a_01xz = 4'b01xz ;
a_1xz = 4'b11xz ;
a_0xz = 4'b00xz ;
a_0dd1 = 4'b1110 ;
a_even1 = 4'b1100 ;
$display("-------------------------------------------");
$display(" a_1 a_0 a_01xz a_1xz a_0xz ");
$display("-------------------------------------------");
$display("& %b %b %b %b
%b ",&a_1,&a_0,&a_01xz,&a_1xz,&a_0xz);
$display("| %b %b %b %b %b ",|a_1,|a_0,|a_01xz,|a_1xz,|a_0xz);
$display("~& %b %b %b %b
%b ",~&a_1,~&a_0,~&a_01xz,~&a_1xz,~&a_0xz);
$display("~| %b %b %b %b %b ",~|a_1,~|a_0,~|a_01xz,~|a_1xz,~|a_0xz);
$display("-------------------------------------------");
$display(" a_ood1 a_even1 a_1xz");
$display("-------------------------------------------");
$display(" ^ %b %b %b ",^a_0dd1,^a_even1,^a_1xz);
$display(" ~^ %b %b %b ",~^a_0dd1,~^a_even1,~^a_1xz);
$display("-------------------------------------------");
end
endprogram
RESULTS
-------------------------------------------
a_1 a_0 a_01xz a_1xz a_0xz
-------------------------------------------
& 1 0 0 x 0
| 1 0 1 1 x
~& 0 1 1 x 1
~| 0 1 0 0 x
-------------------------------------------
a_ood1 a_even1 a_1xz
-------------------------------------------
^ 1 0 x
~^ 0 1 x
-------------------------------------------
Shift :
The left shift operators, << and <<<, shall shift their left operand to the left by the number by the
number of bit positions given by the right operand. In both cases, the vacated bit positions shall
be filled with zeros. The right shift operators, >> and >>>, shall shift their left operand to the
right by the number of bit positions given by the right operand. The logical right shift shall fill
the vacated bit positions with zeros. The arithmetic right shift shall fill the vacated bit positions
with zeros if the result type is unsigned. It shall fill the vacated bit positions with the value of the
most significant (i.e., sign) bit of the left operand if the result type is signed. If the right operand
has an x or z value, then the result shall be unknown. The right operand is always treated.
EXAMPLE :Shift
program main ;
integer a_1,a_0;
initial begin
a_1 = 4'b1100 ;
a_0 = 4'b0011 ;
# ++ increment
# -- decrement
SystemVerilog includes the C increment and decrement assignment operators ++i, --i, i++, and i-
-. These do not need parentheses when used in expressions. These increment and decrement
assignment operators behave as blocking assignments.
The ordering of assignment operations relative to any other operation within an expression is
undefined. An implementation can warn whenever a variable is both written and read-or-written
within an integral expression or in other contexts where an implementation cannot guarantee
order of evaluation.
For example:
i = 10;
j = i++ + (i = i - 1);
After execution, the value of j can be 18, 19, or 20 depending upon the relative ordering of the
increment and the assignment statements. The increment and decrement operators, when applied
to real operands, increment or decrement the operand by 1.0.
a_1 is 21 a_0 is 19
Set :
OPERATOR PRECEDENCY
() Highest precedence
++ --
& ~& | ~| ^ ~^ ~ >< -
(unary)
*/%
+-
<< >>
< <= > >= in !in dist
=?= !?= == != === !==
& &~
^ ^~
| |~
&&
||
?:
= += -= *= /= %=
<<= >>= &= |= ^= ~&= ~|= ~^= Lowest precedence
EVENTS
An identifier declared as an event data type is called a named event. Named event is a data type
which has no storage. In verilog, a named event can be triggered explicitly using "->" . Verilog
Named Event triggering occurrence can be recognized by using the event control "@" . Named
events and event control give a powerful and efficient means of describing the communication
between, and synchronization of, two or more concurrently active processes.
SystemVerilog named events support the same basic operations as verilog named event, but
enhance it in several ways.
Triggered
The "triggered" event property evaluates to true if the given event has been triggered in the
current time-step and false otherwise. If event_identifier is null, then the triggered event property
evaluates to false. Using this mechanism, an event trigger shall unblock the waiting process
whether the wait executes before or at the same simulation time as the trigger operation.
In the following example, event "e" is triggered at time 20,40,60,80 . So the Value of
"e.triggered" should be TRUE at time 20,40,60,80 and FALSE at rest of the time.
EXAMPLE:
module main;
event e;
initial
repeat(4)
begin
#20;
->e ;
$display(" e is triggered at %t ",$time);
end
initial
#100 $finish;
always
begin
#10;
if(e.triggered)
$display(" e is TRUE at %t",$time);
else
$display(" e is FALSE at %t",$time);
end
endmodule
RESULT
e is FALSE at 10
e is triggered at 20
e is TRUE at 20
e is FALSE at 30
e is triggered at 40
e is TRUE at 40
e is FALSE at 50
e is triggered at 60
e is TRUE at 60
e is FALSE at 70
e is triggered at 80
e is TRUE at 80
e is FALSE at 90
Wait()
In SystemVerilog , Named Event triggering occurrence can also be recognized by using the
event control wait(). Wait() statement gets blocked until it evaluates to TRUE. As we have seen
in the previous example, that "event_name.triggered" returns the trigging status of the event in
the current time step.
EXAMPLE:
module event_m;
event a;
initial
repeat(4)
#20 -> a;
always
begin
@a;
$display(" ONE :: EVENT A is triggered ");
end
always
begin
wait(a.triggered);
$display(" TWO :: EVENT A is triggered ");
#1;
end
endmodule
RESULT:
Race Condition
For a trigger to unblock a process waiting on an event, the waiting process must execute the @
statement before the triggering process executes the trigger operator, ->. If the trigger executes
first, then the waiting process remains blocked.
Using event_name.triggered statement, an event trigger shall unblock the waiting process
whether the wait executes before or at the same simulation time as the trigger operation. The
triggered event property, thus, helps eliminate a common race condition that occurs when both
the trigger and the wait (using @) happen at the same time. A process that blocks waiting for an
event might or might not unblock, depending on the execution order of the waiting and triggering
processes (race condition) . However, a process that waits on the triggered state always unblocks,
regardless of the order of execution of the wait and trigger operations.
In the following example, event "e1" is triggered and a process is waiting on "e1" in the same
time step. The process can never catch the triggering of "e1" as it occurs after the event "e1"
triggering. Event "e2" triggering occurrence can be recognized by wait (e2.triggered) in spite of
the above condition.
EXAMPLE:
module main;
event e1,e2;
initial
repeat(4)
begin
#20;
->e1 ;
@(e1)
$display(" e1 is triggered at %t ",$time);
end
initial
repeat(4)
begin
#20;
->e2 ;
wait(e2.triggered);
$display(" e2 is triggered at %t ",$time);
end
endmodule
RESULT
e2 is triggered at 20
e2 is triggered at 40
e2 is triggered at 60
e2 is triggered at 80
Nonblocking Event Trigger
Nonblocking events are triggered using the ->> operator. The effect of the ->> operator is that
the statement executes without blocking and it creates a nonblocking assign update event in the
time in which the delay control expires, or the event-control occurs. The effect of this update
event shall be to trigger the referenced event in the nonblocking assignment region of the
simulation cycle.
Merging Events
An event variable can be assigned to another event variable. When a event variable is assigned to
other , both the events point to same synchronization object. In the following example, Event "a"
is assigned to event "b" and when event "a" is triggered, event occurrence can be seen on event
"b" also.
EXAMPLE:
module events_ab;
event a,b;
initial begin
#1 -> b; // trigger both always blocks
-> a;
#10 b = a; // merge events
#20 -> a; // both will trigger , 3 trigger events but have 4 trigger responses.
end
always@(a) begin
$display(" EVENT A is triggered ");
#20;
end
always@(b) begin
$display(" EVENT B is triggered ");
#20;
end
endmodule
RESULTS:
EVENT B is triggered
EVENT A is triggered
EVENT B is triggered
EVENT A is triggered
When events are merged, the assignment only affects the execution of subsequent event control
or wait operations. If a process is blocked waiting for event1 when another event is assigned to
event1, the currently waiting process shall never unblock. In the following example,
"always@(b)" is waiting for the event on "b" before the assignment "b = a" and this waiting
always block was never unblocked.
EXAMPLE:
module events_ab;
event a,b;
initial
begin
#20 -> a;
b = a;
#20 -> a;
end
always@(a)
$display(" EVENT A is triggered ");
always@(b)
$display(" EVENT B is also triggered ");
endmodule
RESULTS:
EVENT A is triggered
EVENT A is triggered
Null Events
SystemVerilog event variables can also be assigned a null object, when assigned null to event
variable, the association between the synchronization object and the event variable is broken.
EXAMPLE:
program main;
event e;
initial
begin
repeat(4)
#($random()%10) -> e;
e = null;
repeat(4)
#($random()%10) -> e;
end
initial
forever
begin
@e ;
$display(" e is triggered at %t",$time);
end
endprogram
RESULT:
e is triggered at 348
e is triggered at 4967
e is triggered at 9934
e is triggered at 14901
** ERROR ** Accessed Null object
Wait Sequence
The wait_order construct suspends the calling process until all of the specified events are
triggered in the given order (left to right) or any of the un-triggered events are triggered out of
order and thus causes the operation to fail. Wait_order() does not consider time, only ordering in
considered.
EXAMPLE:
module main;
event e1,e2,e3;
initial
begin
#10;
-> e1;
-> e2;
-> e3;
#10;
-> e3;
-> e1;
-> e2;
#10;
-> e3;
-> e2;
-> e3;
end
always
begin
wait_order(e1,e2,e3)
$display(" Events are in order ");
else
$display(" Events are out of order ");
end
endmodule
RESULT:
Events are in order
Events are out of order
Events are out of order
Events Comparison
Event variables can be compared against other event variables or the special value null. Only the
following operators are allowed for comparing event variables:
-- Equality (==) with another event or with null.
-- Inequality (!=) with another event or with null.
-- Case equality (===) with another event or with null (same semantics as ==).
-- Case inequality (!==) with another event or with null (same semantics as !=).
-- Test for a Boolean value that shall be 0 if the event is null and 1 otherwise.
EXAMPLE:
module main;
event e1,e2,e3,e4;
initial
begin
e1 = null;
e2 = e3;
if(e1)
$display(" e1 is not null ");
else
$display(" e1 is null ");
if(e2)
$display(" e2 is not null");
else
$display(" e2 is null");
if(e3 == e4)
$display( " e3 and e4 are same events ");
else
$display( " e3 and e4 are not same events ");
if(e3 == e2)
$display( " e3 and e2 are same events ");
else
$display( " e3 and e2 are not same events ");
end
endmodule
RESULT:
e1 is null
e2 is not null
e3 and e4 are not same events
e3 and e2 are same events
CONTROL STATEMENTS
Sequential Control:
Statements inside sequential control constructs are executed
sequentially.
- if-else Statement
- case Statement
- repeat loop
- for loop
- while loop
- do-while
- foreach
- Loop Control
- randcase Statements
if-else Statement : The if-else statement is the general form of selection statement.
repeat loop : Repeat statements can be used to repeat the execution of a statement or
statement block a fixed number of times.
foreach : foreach construct specifies iteration over the elements of an single dimensional
fixed-size arrays, dynamic arrays and SmartQs.
Loop Control : The break and continue statements are used for flow control within
loops.
EXAMPLE : if
program main ;
integer i;
initial begin
i = 20;
if( i == 20)
$display(" I is equal to %d ",i);
else
$display(" I is not equal to %d ",i);
end
endprogram
RESULTS
I is equal to 20 EXAMPLE : case and repeat
program main ;
integer i;
initial begin
repeat(10)begin
i = $random();
case(1) begin
(i<0) :$display(" i is less than zero i==%d\n",i);
(i>0) :$display(" i is grater than zero i=%d\n",i);
(i == 0):$display(" i is equal to zero i=%d\n",i);
end
end
end
endprogram
RESULTS
i is grater than zero i=69120
i is grater than zero i=475628600
i is grater than zero i=1129920902
i is grater than zero i=773000284
i is grater than zero i=1730349006
i is grater than zero i=1674352583
i is grater than zero i=1662201030
i is grater than zero i=2044158707
i is grater than zero i=1641506755
i is grater than zero i=797919327
EXAMPLE : forloop
program for_loop;
integer count, i;
initial begin
for(count = 0, i=0; i*count<50; i++, count++)
$display("Value i = %0d\n", i);
end
endprogram
RESULTS
Value i = 0
Value i = 1
Value i = 2
Value i = 3
Value i = 4
Value i = 5
Value i = 6
Value i = 7
EXAMPLE : whileloop
program while_loop;
integer operator=0;
initial begin
while (operator<5)begin
operator += 1;
$display("Operator is %0d\n", operator);
end
end
endprogram
RESULTS
Operator is 1
Operator is 2
Operator is 3
Operator is 4
Operator is 5
EXAMPLE : dowhile
program test;
integer i = 0;
initial begin
do
begin
$display("i = %0d \n", i);
i++;
end
while (i < 10);
end
endprogram
RESULTS
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
The foreach construct specifies iteration over the elements of an array. Its argument is an
identifier that designates any type of array (fixed-size, dynamic, or associative) followed by a list
of loop variables enclosed in square brackets. Each loop variable corresponds to one of the
dimensions of the array. The foreach construct is similar to a repeat loop that uses the array
bounds to specify the repeat count instead of an expression.
The mapping of loop variables to array indexes is determined by the dimension cardinality, as
described in multidimentional topic.
The foreach arranges for higher cardinality indexes to change more rapidly.
// 1 2 3 3 4 1 2 -> Dimension numbers
int A [2][3][4]; bit [3:0][2:1] B [5:1][4];
foreach( A [ i, j, k ] ) ...
foreach( B [ q, r, , s ] ) ...
The first foreach causes i to iterate from 0 to 1, j from 0 to 2, and k from 0 to 3. The second
foreach causes q to iterate from 5 to 1, r from 0 to 3, and s from 2 to 1 (iteration over the third
index is skipped).
EXAMPLE : foeach
program example;
string names[$]={"Hello", "SV"};
int fxd_arr[2][3] = '{'{1,2,3},'{4,5,6}};
initial begin
foreach (names[i])
$display("Value at index %0d is %0s\n", i, names[i]);
foreach(fxd_arr[,j])
$display(fxd_arr[1][j]);end
endprogram
RESULTS
Value at index 0 is Hello
Value at index 1 is SV
4
5
6
EXAMPLE : randcase
program rand_case;
integer i;
initial begin
repeat(10)begin
randcase
begin
10: i=1;
20: i=2;
50: i=3;
end
$display(" i is %d \n",i);end
end
endprogram
RESULTS
i is 3
i is 2
i is 3
i is 3
i is 3
i is 3
i is 1
i is 1
i is 1
i is 2
Enhanced For Loop
In Verilog, the variable used to control a for loop must be declared prior to the loop. If loops in
two or more parallel procedures use the same loop control variable, there is a potential of one
loop modifying the variable while other loops are still using it.
SystemVerilog adds the ability to declare the for loop control variable within the for loop. This
creates a local variable within the loop. Other parallel loops cannot inadvertently affect the loop
control variable.
For example:
module foo;
initial begin
for (int i = 0; i <= 255; i++)
...
end
initial begin
loop2: for (int i = 15; i >= 0; i--)
...
end
endmodule
Unique:
A unique if asserts that there is no overlap in a series of if...else...if conditions, i.e., they are
mutually exclusive and hence it is safe for the expressions to be evaluated in parallel. In a unique
if, it shall be legal for a condition to be evaluated at any time after entrance into the series and
before the value of the condition is needed. A unique if shall be illegal if, for any such
interleaving of evaluation and use of the conditions, more than one condition is true. For an
illegal unique if, an implementation shall be required to issue a warning, unless it can
demonstrate a legal interleaving so that no more than one condition is true.
EXAMPLE :
module uniq;
initial
begin
for (int a = 0;a< 6;a++)
unique if ((a==0) || (a==1) ) $display("0 or 1");
else if (a == 2) $display("2");
else if (a == 4) $display("4"); // values 3,5,6 cause a warning
end
endmodule
RESULTS:
0 or 1
0 or 1
2
RT Warning: No condition matches in 'unique if' statement.
4
RT Warning: No condition matches in 'unique if' statement.
Priority:
A priority if indicates that a series of if...else...if conditions shall be evaluated in the order listed.
In the preceding example, if the variable a had a value of 0, it would satisfy both the first and
second conditions, requiring priority logic. An implementation shall also issue a warning if it
determines that no condition is true, or it is possible that no condition is true, and the final if does
not have a corresponding else.
EXAMPLE:
module prioriti;
initial
for(int a = 0;a<7;a++)
priority if (a[2:1]==0) $display("0 or 1");
else if (a[2] == 0) $display("2 or 3");
else $display("4 to 7"); //covers all other possible values, so no warning
endmodule
RESULTS:
0 or 1
0 or 1
2 or 3
2 or 3
4 to 7
4 to 7
4 to 7
If the case is qualified as priority or unique, the simulator shall issue a warning message if no
case item matches. These warnings can be issued at either compile time or run time, as soon as it
is possible to determine the illegal condition.
EXAMPLE:
module casee;
initial
begin
for(int a = 0;a<4;a++)
unique case(a) // values 3,5,6,7 cause a warning
0,1: $display("0 or 1");
2: $display("2");
4: $display("4");
endcase
for(int a = 0;a<4;a++)
priority casez(a) // values 4,5,6,7 cause a warning
3'b00?: $display("0 or 1");
3'b0??: $display("2 or 3");
endcase
end
endmodule
RESULTS:
0 or 1
0 or 1
2
Warning: No condition matches in 'unique case' statement.
0 or 1
0 or 1
2 or 3
2 or 3
PROGRAM BLOCK
The module is the basic building block in Verilog which works well for Design. However, for
the testbench, a lot of effort is spent getting the environment properly initialized and
synchronized, avoiding races between the design and the testbench, automating the generation of
input stimuli, and reusing existing models and other infrastructure.
Systemverilog adds a new type of block called program block. It is declared using program and
endprogram keywords.
The program block serves these basic purposes:
-> Separates the testbench from the DUT.
-> The program block helps ensure that test bench transitions do not have race conditions with
the design
-> It provides an entry point to the execution of testbenches.
-> It creates a scope that encapsulates program-wide data.
-> It provides a syntactic context that specifies scheduling in the Reactive region which avoids
races.
-> It doesnot allow always block. Only initial and methods are allowed, which are more
controllable.
-> Each program can be explicitly exited by calling the $exit system task. Unlike $finish, which
exits simulation immediately, even if there are pending events.
-> Just like a module, program block has ports. One or more program blocks can be instantiated
in a top-level netlist, and connected to the DUT.
The program construct serves as a clear separator between design and testbench, and, more
importantly, it specifies specialized execution semantics in the Reactive region for all elements
declared within the program. Together with clocking blocks, the program construct provides for
race-free interaction between the design and the testbench, and enables cycle and transaction
level abstractions.
For example:
program test (input clk, input [16:1] addr, inout [7:0] data);
initial ...
endprogram
program test ( interface device_ifc );
initial ...
endprogram
program schedules events in the Reactive region, the clocking block construct is very useful to
automatically sample the steady-state values of previous time steps or clock cycles. Programs
that read design values exclusively through clocking blocks with #0 input skews are insensitive
to read-write races. It is important to note that simply sampling input signals (or setting non-zero
skews on clocking block inputs) does not eliminate the potential for races. Proper input sampling
only addresses a single clocking block. With multiple clocks, the arbitrary order in which
overlapping or simultaneous clocks are processed is still a potential source for races.
Following example demonstrates the difference between the module based testbench and
program based testbenchs
module DUT();
reg q = 0;
reg clk = 0;
initial
#10 clk = 1;
always @(posedge clk)
q <= 1;
endmodule
module Module_based_TB();
always @ (posedge DUT.clk) $display("Module_based_TB : q = %b\n", DUT.q);
endmodule
program Program_based_TB();
initial
forever @(posedge DUT.clk) $display("Program_based_TB : q = %b\n", DUT.q);
endprogram
RESULT:
Module_based_TB : q = 0
program_based_TB : q = 1
PROCEDURAL BLOCKS
Final:
Verilog procedural statements are in initial or always blocks, tasks, or functions. SystemVerilog
adds a final block that executes at the end of simulation.SystemVerilog final blocks execute in an
arbitrary but deterministic sequential order. This is possible because final blocks are limited to
the legal set of statements allowed for functions.
EXAMPLE :
module fini;
initial
#100 $finish;
final
$display(" END OF SIMULATION at %d ",$time);
endmodule
RESULTS:
END OF SIMULATION at 100
Jump Statements:
SystemVerilog has statements to control the loop statements.
break : to go out of loop as C
continue : skip to end of loop as C
return expression : exit from a function
return : exit from a task or void function
Event Control:
Any change in a variable or net can be detected using the @ event control, as in Verilog. If the
expression evaluates to a result of more than 1 bit, a change on any of the bits of the result
(including an x to z change) shall trigger the event control.
SystemVerilog adds an iff qualifier to the @ event control.
EXAMPLE:
module latch (output logic [31:0] y, input [31:0] a, input enable);
always @(a iff enable == 1)
y <= a; //latch is in transparent mode
endmodule
Always:
In an always block that is used to model combinational logic, forgetting an else leads to an
unintended latch. To avoid this mistake, SystemVerilog adds specialized always_comb and
always_latch blocks, which indicate design intent to simulation, synthesis, and formal
verification tools. SystemVerilog also adds an always_ff block to indicate sequential logic.
EXAMPLE:
always_comb
a = b & c;
always_latch
if(ck) q <= d;
always_ff @(posedge clock iff reset == 0 or posedge reset)
r1 <= reset ? 0 : r2 + 1;FORK JOIN
A Verilog fork...join block always causes the process executing the fork statement to block until
the termination of all forked processes. With the addition of the join_any and join_none
keywords, SystemVerilog provides three choices for specifying when the parent (forking)
RESULTS
BEFORE fork time = 10
time = 15 # 5
time = 15 Outside the main fork
In the above example, Simulation ends before the #10 and #20 gets executed. In some situations,
we need to wait until all the threads got finished to start the next task. Using wait fork, will block
the till all the child processes complete.
EXAMPLE:
program main();
initial begin
#(10);
$display(" BEFORE fork time = %0d ",$time );
fork
begin
# (20);
$display(" time = %0d # 20 ",$time );
end
begin
#(10);
$display(" time = %0d # 10 ",$time );
end
begin
#(5);
$display(" time = %0d # 5 ",$time );
end
join_any
$display(" time = %0d Outside the main fork ",$time );
wait fork ;
$display(" time = %0d After wait fork ",$time );
end
endprogram
RESULTS
BEFORE fork time = 10
time = 15 # 5
time = 15 Outside the main fork
time = 20 # 10
time = 30 # 20
time = 30 After wait fork
Disable Fork Statement
The disable fork statement terminates all active descendants (subprocesses) of the calling
process.
In other words, if any of the child processes have descendants of their own, the disable fork
statement shall terminate them as well. Sometimes, it is required to kill the child processes after
certain condition.
EXAMPLE
program main();
initial begin
#(10);
$display(" BEFORE fork time = %0d ",$time );
fork
begin
# (20);
$display(" time = %0d # 20 ",$time );
end
begin
#(10);
$display(" time = %0d # 10 ",$time );
end
begin
#(5);
$display(" time = %0d # 5 ",$time );
end
join_any
$display(" time = %0d Outside the main fork ",$time );
end
initial
#100 $finish;
endprogram
RESULTS
BEFORE fork time = 10
time = 15 # 5
time = 15 Outside the main fork
time = 20 # 10
time = 30 # 20
In the following example, disable for kills the threads #10 and #20.
EXAMPLE
program main();
initial begin
#(10);
$display(" BEFORE fork time = %0d ",$time );
fork
begin
# (20);
$display(" time = %0d # 20 ",$time );
end
begin
#(10);
$display(" time = %0d # 10 ",$time );
end
begin
#(5);
$display(" time = %0d # 5 ",$time );
end
join_any
$display(" time = %0d Outside the main fork ",$time );
disable fork;
$display(" Killed the child processes");
end
initial
#100 $finish;
endprogram
RESULTS
BEFORE fork time = 10
time = 15 # 5
time = 15 Outside the main fork
Killed the child processes
SUBROUTINES
Begin End
With SystemVerilog, multiple statements can be written between the task declaration and
endtask, which means that the begin .... end can be omitted. If begin .... end is omitted,
statements are executed sequentially, the same as if they were enclosed in a begin .... end group.
It shall also be legal to have no statements at all.
Tasks:
A Verilog task declaration has the formal arguments either in parentheses or in declaration.
task mytask1 (output int x, input logic y);
With SystemVerilog, there is a default direction of input if no direction has been specified. Once
a direction is given, subsequent formals default to the same direction. In the following example,
the formal arguments a and b default to inputs, and u and v are both outputs.
task mytask3(a, b, output logic [15:0] u, v);
Return In Tasks
In Verilog, a task exits when the endtask is reached. With SystemVerilog, the return statement
can be used to exit the task before the endtask keyword.
In the following example, Message "Inside Task : After return statement" is not executed
because the task exited before return statement.
EXAMPLE
program main();
task task_return();
$display("Inside Task : Before return statement");
return;
$display("Inside Task : After return statement");
endtask
initial
task_return();
endprogram
RESULT:
Inside Task : Before return statement
Functions:
function logic [15:0] myfunc1(int x, int y);
Function declarations default to the formal direction input if no direction has been specified.
Once a direction is given, subsequent formals default to the same direction. In the following
example, the formal arguments a and b default to inputs, and u and v are both outputs:
function logic [15:0] myfunc3(int a, int b, output logic [15:0] u, v);
Return Values And Void Functions::
SystemVerilog allows functions to be declared as type void, which do not have a return value.
For nonvoid functions, a value can be returned by assigning the function name to a value, as in
Verilog, or by using return with a value. The return statement shall override any value assigned
to the function name. When the return statement is used, nonvoid functions must specify an
expression with the return.
EXAMPLE:
function [15:0] myfunc2 (input [7:0] x,y);
return x * y - 1; //return value is specified using return statement
endfunction
// void functions
function void myprint (int a);
Pass By Reference: In verilog,method arguments takes as pass by value.The inputs are copyed
when the method is called and the outputs are assigned to outputs when exiting the method.In
SystemVerilog ,methods can have pass by reference.Arguments passed by reference are not
copied into the subroutine area, rather, a reference to the original argument is passed to the
subroutine. The subroutine can then access the argument data via the reference.
In the following example, variable a is changed at time 10,20 and 30. The method pass_by_val ,
copies only the value of the variable a, so the changes in variable a which are occurred after the
task pass_by_val call, are not visible to pass_by_val. Method pass_by_ref is directly referring to
the variable a. So the changes in variable a are visible inside pass_by_ref.
EXAMPLE:
program main();
int a;
initial
begin
#10 a = 10;
#10 a = 20;
#10 a = 30;
#10 $finish;
end
task pass_by_val(int i);
forever
@i $display("pass_by_val: I is %0d",i);
endtask
task pass_by_ref(ref int i);
forever
@i $display("pass_by_ref: I is %0d",i);
endtask
initial
pass_by_val(a);
initial
pass_by_ref(a);
endprogram
RESULT
pass_by_ref: I is 10
pass_by_ref: I is 20
pass_by_ref: I is 30
Default Values To Arguments:
SystemVerilog allows to declare default values to arguments.When the subrotunies are
called,arguments those are omited,will take default value.
EXAMPLE:
program main();
task display(int a = 0,int b,int c = 1 );
$display(" %0d %0d %0d ",a,b,c);
endtask
initial
begin
display( , 5 ); // is equivalent to display( 0, 5, 1 );
display( 2, 5 ); // is equivalent to display( 2, 5, 1 );
display( , 5, ); // is equivalent to display( 0, 5, 1 );
display( , 5, 7 ); // is equivalent to display( 0, 5, 7 );
display( 1, 5, 2 ); // is equivalent to display( 1, 5, 2 );
end
endprogram
RESULT:
051
251
051
057
152
Argument Binding By Name
SystemVerilog allows arguments to tasks and functions to be bound by name as well as by
position. This allows specifying non-consecutive default arguments and easily specifying the
argument to be passed at the call.
EXAMPLE:
program main();
function void fun( int j = 1, string s = "no" );
$display("j is %0d : s is %s ",j,s);
endfunction
initial
begin
fun( .j(2), .s("yes") ); // fun( 2, "yes" );
fun( .s("yes") ); // fun( 1, "yes" );
fun( , "yes" ); // fun( 1, "yes" );
fun( .j(2) ); // fun( 2, "no" );
fun( .s("yes"), .j(2) ); // fun( 2, "yes" );
fun( .s(), .j() ); // fun( 1, "no" );
fun( 2 ); // fun( 2, "no" );
fun( ); // fun( 1, "no" );
end
endprogram
RESULT
j is 2 : s is yes
j is 1 : s is yes
j is 1 : s is yes
j is 2 : s is no
j is 2 : s is yes
j is 1 : s is no
j is 2 : s is no
j is 1 : s is no
Optional Argument List
When a task or function specifies no arguments, the empty parenthesis, (), following the
task/function name shall be optional. This is also true for tasks or functions that require
arguments, when all arguments have defaults
specified.
EXAMPLE
program main();
function void fun( );
$display("Inside function");
endfunction
initial
begin
fun( );
fun;
end
endprogram
RESULT
Inside function
Inside function
SEMAPHORE
Conceptually, a semaphore is a bucket. When a semaphore is allocated, a bucket that contains a
fixed number of keys is created. Processes using semaphores must first procure a key from the
bucket before they can continue to execute. If a specific process requires a key, only a fixed
number of occurrences of that process can be in progress simultaneously. All others must wait
until a sufficient number of keys is returned to the bucket. Semaphores are typically used for
mutual exclusion, access control to shared resources, and basic synchronization.
Semaphore is a built-in class that provides the following methods:
-- Create a semaphore with a specified number of keys: new()
-- Obtain one or more keys from the bucket: get()
-- Return one or more keys into the bucket: put()
-- Try to obtain one or more keys without blocking: try_get()
EXAMPLE:semaphore
program main ;
semaphore sema = new(1);
initial begin
repeat(3) begin
fork
////////// PROCESS 1 ////////////////
begin
$display("1: Waiting for key");
sema.get(1);
$display("1: Got the Key");
#(10);// Do some work
sema.put(1);
$display("1: Returning back key ");
#(10);
end
////////// PROCESS 2 ////////////////
begin
$display("2: Waiting for Key");
sema.get(1);
$display("2: Got the Key");
#(10);//Do some work
sema.put(1);
$display("2: Returning back key ");
#(10);
end
join
end
#1000;
end
endprogram
RESULTS:
1: Waiting for key
1: Got the Key
2: Waiting for Key
1: Returning back key
2: Got the Key
2: Returning back key
1: Waiting for key
1: Got the Key
2: Waiting for Key
1: Returning back key
2: Got the Key
2: Returning back key
1: Waiting for key
1: Got the Key
2: Waiting for Key
1: Returning back key
2: Got the Key
2: Returning back key
MAILBOX
A mailbox is a communication mechanism that allows messages to be exchanged between
processes. Data can be sent to a mailbox by one process and retrieved by another.
Mailbox is a built-in class that provides the following methods:
--Create a mailbox: new()
--Place a message in a mailbox: put()
--Try to place a message in a mailbox without blocking: try_put()
--Retrieve a message from a mailbox: get() or peek()
--Try to retrieve a message from a mailbox without blocking: try_get() or try_peek()
--Retrieve the number of messages in the mailbox: num()
EXAMPLE:
program meain ;
mailbox my_mailbox;
initial begin
my_mailbox = new();
if (my_mailbox)
begin
fork
put_packets();
get_packets();
#10000;
join_any
end
#(1000);
$display("END of Program");
end
task put_packets();
integer i;
begin
for (i=0; i<10; i++)
begin
#(10);
my_mailbox.put(i);
$display("Done putting packet %d @time %d",i, $time);
end
end
endtask
task get_packets();
integer i,packet;
begin
for (int i=0; i<10; i++)
begin
my_mailbox.get(packet);
$display("Got packet %d @time %d", packet, $time);
end
end
endtask
endprogram
RESULTS:
Done putting packet 0 @time 10
Got packet 0 @time 10
Done putting packet 1 @time 20
Got packet 1 @time 20
Done putting packet 2 @time 30
Got packet 2 @time 30
Done putting packet 3 @time 40
Got packet 3 @time 40
Done putting packet 4 @time 50
Got packet 4 @time 50
Done putting packet 5 @time 60
Got packet 5 @time 60
Done putting packet 6 @time 70
Got packet 6 @time 70
Done putting packet 7 @time 80
Got packet 7 @time 80
Done putting packet 8 @time 90
Got packet 8 @time 90
Done putting packet 9 @time 100
Got packet 9 @time 100
END of Program
suspend
The suspend() task allows a process to suspend either its own execution or that of another
process. If the process to be suspended is not blocked waiting on some other condition, such as
an event, wait expression, or a delay then the process shall be suspended at some unspecified
time in the current time step. Calling this method more than once, on the same (suspended)
process, has no effect.
resume
The resume() task restarts a previously suspended process. Calling resume on a process that was
suspended while blocked on another condition shall re-sensitize the process to the event
expression, or wait for the wait condition to become true, or for the delay to expire. If the wait
condition is now true or the original delay has transpired, the process is scheduled onto the
Active or Reactive region, so as to continue its execution in the current time step. Calling resume
on a process that suspends itself causes the process to continue to execute at the statement
following the call to suspend.
The example below starts an arbitrary number of processes, as specified by the task argument N.
Next, the task waits for the last process to start executing, and then waits for the first process to
terminate. At that point the parent process forcibly terminates all forked processes that have not
completed yet.
end
endtask
INTERFACE
The communication between blocks of a digital system is a critical area. In Verilog, modules are
connected using module ports. For large modules, this is not productive as it involves
Manually connecting hundreds of ports may lead to errors.
Detailed knowledge of all the port is required.
Difficult to change if the design changes.
More time consuming.
Most port declaration work is duplicated in many modules.
Let us see a verilog example:
module Dut (input clk, read, enable,
Input [7:0] addr,
output [7:0] data);
....
assign data = temp1 ? temp2 : temp3 ;
always @(posedge clk)
....
endmodule
module Testbench(input clk,
Output read, enable,
output [7:0] addr,
input [7:0] data );
endmodule
endmodule
module Testbench(intf tb_if);
initial
begin
tb_if.read = 0;
repeat(3) #20 tb_if.read = ~tb_if.read;// driving a signal
$finish;
end
endmodule
Integrating the above two modules in top module.
module top();
bit clk;
initial
forever #5 clk = ~clk;
intf bus_if(clk); // interface instantiation
Dut d(bus_if); // use interface for connecting D and TB
Testbench TB (bus_if);
endmodule
See, how much code we got reduced in this small example itself. In the above example, I
demonstrated the connectivity between DUT and TestBench. Interfaces can also be used for
connectivity between the DUT sub modules also.
1 module top();
2 logic clk;
3 intf bus_if(clk); // interface instantiation
4 Dut d(bus_if); // Pass the interface
5 Testbench TB (Bus_if); // Pass the interface
6 endmodule
Modport Selection Duing Module Instance.
module Dut (intf dut_if); // declaring the interface ....
assign dut_if.data = temp1 ? temp2 : temp3 ; // using the signal in interface
always @(posedge intf.clk)
....
endmodule
module Testbench(intf tb_if);
.....
.....
endmodule
1 module top();
2 logic clk;
3 intf bus_if(clk); // interface instantiation
4 Dut d(bus_if.dut); // Pass the modport into the module
5 Testbench TB (Bus_if.tb); // Pass the modport into the module
6 endmodule
A mod port can also define expressions. They can also define their own names. Module can use
the modport declared name. For example
modport dut (input read,enable,.addr(2),output .d(data[1:5]);
module dut(intf.dut dut_if);
assign dut_if.d = temp; // using the signal name declared by modport.
INTERFACE METHODS
Methods In Interfaces
Interfaces can include task and function definitions. This allows a more abstract level of
modeling.
interface intf (input clk);
logic read, enable,
logic [7:0] addr,data;
task masterRead(input logic [7:0] raddr); // masterRead method
...
endtask: masterRead
task slaveRead; // slaveRead method
...
endtask: slaveRead
endinterface :intf
CLOCKING BLOCK
Clocking Blocks
SystemVerilog adds the clocking block that identifies clock signals, and captures the timing and
synchronization requirements of the blocks being modeled. A clocking block assembles signals
that are synchronous to a particular clock, and makes their timing explicit. The clocking block is
a key element in a cycle-based methodology, which enables users to write testbenches at a higher
level of abstraction. Simulation is faster with cycle based methodology.
Depending on the environment, a testbench can contain one or more clocking blocks, each
containing its own clock plus an arbitrary number of signals. These operations are as follows:
Synchronous events
Input sampling
Synchronous drives
clocking cb @(posedge clk);
default input #10ns output #2ns;
output read,enable,addr;
input negedge data;
endclocking
In the above example, the first line declares a clocking block called cb that is to be clocked on
the positive edge of the signal clk. The second line specifies that by default all signals in the
clocking block shall use a 10ns input skew and a 2ns output skew by default. The next line adds
three output signals to the clocking block: read, enable and addr. The fourth line adds the signal
data to the clocking block as input. Fourth line also contains negedge which overrides the skew
,so that data is sampled on the negedge of the clk.
Skew
If an input skew is specified then the signal is sampled at skew time units before the clock event.
If output skew is specified, then output (or inout) signals are driven skew time units after the
corresponding clock event. A skew must be a constant expression, and can be specified as a
parameter.
Skew can be specified in 3 ways.
#d : The skew is d time units. The time unit depends on the timescale of the block.
#dns : The skew is d nano seconds.
#1step : Sampling is done in the preponed region of current time stamp.
If skew is not specified, default input skew is 1step and output skew is 0.
Specifying a clocking block using a SystemVerilog interface can significantly reduce the amount
of code needed to connect the TestBench without race condition. Clocking blocks add an extra
level of signal hierarchy while accessing signals.
Interface declaration with clocking block:
interface intf (input clk);
logic read, enable,
logic [7:0] addr,data;
clocking cb @(posedge clock); // clocking block for testbench
default input #10ns output #2ns;
output read,enable,addr;
input data;
endclocking
modport dut (input read,enable,addr,output data);
modport tb (clocking cb); // synchronous testbench modport
endinterface :intf
module testbench(intf.tb tb_if);
......
initial
tb_if.cb.read <= 1; //writing to synchronous signal read
...
endmodule
Cycle Delay
The ## operator can be used to delay execution by a specified number of clocking events, or
clock cycles. What constitutes a cycle is determined by the default clocking in effect of module,
interface, or program.
## <integer_expression>;
##3; // wait 3 cycles
##1 tb_if.addr <= 8'h00;// waits for 1 cycle and then writes address.
Using clocking blocks,cycle delays syntax gets reduced.
Insted of writing
repeat(3) @(posedge clock); sync_block.a <= 1;
Just use
##3 sync_block.a <= 1;
Objects are key to understanding object-oriented technology. Look around right now and you'll
find many examples of real-world objects: your system, your desk, your chair.
Real-world objects share two characteristics: They all have state and behavior. System have state
(name, color) and behavior (playing music,switch off). Identifying the state and behavior for
real-world objects is a great way to begin thinking in terms of object-oriented programming.
Class
It is the central point of OOP and that contains data and codes with behavior. In SystemVerilog
OOPS , everything happens within class and it describes a set of objects with common behavior.
The class definition describes all the properties, behavior, and identity of objects present within
that class.
Object
Objects are the basic unit of object orientation with behavior, identity. As we mentioned above,
these are part of a class but are not the same. An object is expressed by the variable and methods
within the objects. Again these variables and methods are distinguished from each other as
instant variables, instant methods and class variable and class methods.
Methods
We know that a class can define both attributes and behaviors. Again attributes are defined by
variables and behaviors are represented by methods. In other words, methods define the abilities
of an object.
Inheritance
This is the mechanism of organizing and structuring program. Though objects are distinguished
from each other by some additional features but there are objects that share certain things
common. In object oriented programming classes can inherit some common behavior and state
from others. Inheritance in OOP allows to define a general class and later to organize some other
classes simply adding some details with the old class definition. This saves work as the special
class inherits all the properties of the old general class and as a programmer you only require the
new features. This helps in a better data analysis, accurate coding and reduces development time.
In Verilog , to write a new definition using the exesisting definitioan is done inserting `ifdef
conpilation controls into the exesisting code.
Abstraction
The process of abstraction in SystemVerilog is used to hide certain details and only show the
essential features of the object. In other words, it deals with the outside view of an object.
Encapsulation
This is an important programming concept that assists in separating an object's state from its
behavior. This helps in hiding an object's data describing its state from any further modification
by external component. In SystemVerilog there are three different terms used for hiding data
constructs and these are public, private and protected . As we know an object can associated with
data with predefined classes and in any application an object can know about the data it needs to
know about. So any unnecessary data are not required by an object can be hidden by this process.
It can also be termed as information hiding that prohibits outsiders in seeing the inside of an
object in which abstraction is implemented.
Polymorphism
It describes the ability of the object in belonging to different types with specific behavior of
each type. So by using this, one object can be treated like another and in this way it can create
and define multiple level of interface. Here the programmers need not have to know the exact
type of object in advance and this is being implemented at runtime.
CLASS
A class is an actual representation of an Abstrace data type. It therefore provides implementation
details for the data structure used and operations.
EXAMPLE:
class A ;
// attributes:
int i
// methods:
task print
endclass
Definition (Class) : A class is the implementation of an abstract data type . It defines attributes
and methods which implement the data structure and operations of the abstract data type,
respectively. Instances of classes are called objects. Consequently, classes define properties and
behaviour of sets of objects.
Local Variables :
Local Variables Similar to how an object stores its state in fields, a method will often store its
temporary state in local variables. The syntax for declaring a local variable is similar to declaring
a field (for example, int count = 0;). There is no special keyword designating a variable as local;
that determination comes entirely from the location in which the variable is declared , which is
between the opening and closing braces of a method. As such, local variables are only visible to
the methods in which they are declared; they are not accessible from the rest of the class.
Parameters :
The important thing to remember is that parameters are always classified as "variables" not
"fields".
Constants :
Class properties can be made read-only by a const declaration like any other SystemVerilog
variable.
OBJECT
Objects are uniquely identifiable by a name. Therefore you could have two distinguishable
objects with the same set of values. This is similar to traditional programming languages like
verilog where you could have, say two integers i and j both of which equal to 2. Please notice the
use of i and j in the last sentence to name the two integers. We refer to the set of values at a
particular time as the state of the object.
EXAMPLE:
class simple ;
int i;
int j;
task printf();
$display( i , j );
endtask
endclass
program main;
initial
begin
simple obj_1;
simple obj_2;
obj_1 = new();
obj_2 = new();
obj_1.i = 2;
obj_2.i = 4;
obj_1.printf();
obj_2.printf();
end
endprogram
RESULT
2 0
4 0
Definition (Object) An object is an instance of a class. It can be uniquely identified by its name
and it defines a state which is represented by the values of its attributes at a particular time.
The state of the object changes according to the methods which are applied to it. We refer to
these possible sequence of state changes as the behaviour of the object.
Definition (Behaviour) The behaviour of an object is defined by the set of methods which can be
applied on it.
We now have two main concepts of object-orientation introduced, class and object. Object-
oriented programming is therefore the implementation of abstract data types or, in more simple
words, the writing of classes. At runtime instances of these classes, the objects, achieve the goal
of the program by changing their states. Consequently, you can think of your running program as
a collection of objects.
Creating Objects
As you know, a class provides the blueprint for objects; you create an object from a class. In the
following statements ,program creates an object and assigns it to a variable:
packet pkt = new(23, 94);
driver drvr = new(pkt,intf);
The first line creates an object of the packet class, and the second create an object of the driver
class.
Each of these statements has three parts (discussed in detail below):
1. Declaration: The code set in bold are all variable declarations that associate a variable
name with an object type.
2. Instantiation: The new keyword is a SV operator that creates the object.
3. Initialization: The new operator is followed by a call to a constructor, which initializes
the new object.
Declaration:
Declaring a Variable to Refer to an Object
Previously, you learned that to declare a variable, you write:
type name;
This notifies the compiler that you will use name to refer to data whose type is type. With a
primitive variable, this declaration also reserves the proper amount of memory for the variable.
You can also declare a reference variable on its own line. For example:
packet pkt;
If you declare pkt like this, its value will be undetermined until an object is actually created and
assigned to it using the new method. Simply declaring a reference variable does not create an
object. For that, you need to use the new operator. You must assign an object to pkt before you
use it in your code. Otherwise, you will get a compiler error.
A variable in this state, which currently references no object, can be illustrated as follows (the
variable name, pkt, plus a reference pointing to nothing): . During simulation, the tool will not
allocate memory for this object and error is reported. There will not be any compilation error.
EXAMPLE:
class packet ;
int length = 0;
function new (int l);
length = l;
endfunction
endclass
program main;
initial
begin
packet pkt;
pkt.length = 10;
end
endprogram
RESULT
Error: null object access
Instantiating A Class:
The new operator instantiates a class by allocating memory for a new object and returning a
reference to that memory. The new operator also invokes the object constructor.
The new operator returns a reference to the object it created. This reference is usually assigned to
a variable of the appropriate type, like:
packet pkt = new(10);
Initializing An Object
If a class does not explicitly declare any, the SV compiler automatically provides a no-argument
constructor, called the default constructor. This default constructor calls the class parent's no-
argument constructor, or the Object constructor if the class has no other parent.
Constructor
SystemVerilog does not require the complex memory allocation and deallocation of C++.
Construction of an object is straightforward; and garbage collection, as in Java, is implicit and
automatic. There can be no memory leaks or other subtle behavior that is so often the bane of
C++ programmers.
The new operation is defined as a function with no return type, and like any other function, it
must be nonblocking. Even though new does not specify a return type, the left-hand side of the
assignment determines the return type.
THIS
program main;
initial
begin
packet pkt;
pkt =new(10);
$display(pkt.length);
end
endprogram
RESULT
10
Each argument to the second constructor shadows one of the object's fields inside the
constructor "length" is a local copy of the constructor's first argument. To refer to the legnth field
inside the object , the constructor must use "this.length".
INHERITANCE
Object-oriented programming allows classes to inherit commonly used state and behavior from
other classes.
Objects of a subclass can be used where objects of the corresponding superclass are expected.
This is due to the fact that objects of the subclass share the same behaviour as objects of the
superclass. Superclasses are also called parent classes. Subclasses may also be called child
classes or extended classes or just derived classes . Of course, you can again inherit from a
subclass, making this class the superclass of the new subclass. This leads to a hierarchy of
superclass/subclass relationships.
The idea of inheritance is simple but powerful: When you want to create a new class and there is
already a class that includes some of the code that you want, you can derive your new class from
the existing class. In doing this, you can reuse the fields and methods of the existing class
without having to write (and debug!) them yourself.
A subclass inherits all the members (fields, methods, and nested classes) from its superclass.
Constructors are not members, so they are not inherited by subclasses, but the constructor of the
superclass can be invoked from the subclass.
What You Can Do In A Subclass:
A subclass inherits all of the public and protected members of its parent. You can use the
inherited members as is, replace them, hide them, or supplement them with new members:
The inherited fields can be used directly, just like any other fields.
You can declare a field in the subclass with the same name as the one in the superclass, thus
hiding it (not recommended).
You can declare new fields in the subclass that are not in the superclass.
The inherited methods can be used directly as they are.
You can write a new instance method in the subclass that has the same signature as the one in
the superclass, thus overriding it.
You can write a new static method in the subclass that has the same signature as the one in
the superclass, thus hiding it.
You can declare new methods in the subclass that are not in the superclass.
You can write a subclass constructor that invokes the constructor of the superclass, either
implicitly or by using the keyword super.
Overriding
Following example shows, adding new varibles, new methods, redefining existing methods.
EXAMPLE:
class parent;
task printf();
$display(" THIS IS PARENT CLASS ");
endtask
endclass
program main;
initial
begin
parent p;
subclass s;
p = new();
s = new();
p.printf();
s.printf();
end
endprogram
RESULT
RESULT
Subclasses (or derived classes) are classes that are extensions of the current class whereas
superclasses (parent classes or base classes) are classes from which the current class is extended,
beginning with the original base class.
NOTE: When using the super within new, super.new shall be the first statement executed in the
constructor. This is because the superclass must be initialized before the current class and, if the
user code does not provide an initialization, the compiler shall insert a call to super.new
automatically.
Is Only Method
Programmers can override the existing code/functionality before existing code and replaces with
new code as shown in below example.
EXAMPLE:
class parent;
task printf();
$display(" THIS IS PARENT CLASS ");
endtask
endclass
program main;
initial
begin
subclass s;
s = new();
s.printf();
end
endprogram
RESULT:
THIS IS SUBCLASS
Is First Method
Programmers can add new lines of code/functionality before existing code as shown in below
example.
EXAMPLE:
class parent;
task printf();
$display(" THIS IS PARENT CLASS ");
endtask
endclass
program main;
initial
begin
subclass s;
s = new();
s.printf();
end
endprogram
RESULT:
THIS IS SUBCLASS
THIS IS PARENT CLASS
Is Also Method
Programmers can add new lines of code/functionality after the existing code as shown in below
example.
EXAMPLE:
class parent;
task printf();
$display(" THIS IS PARENT CLASS ");
endtask
endclass
class subclass extends parent;
task printf();
super.printf();
$display(" THIS IS SUBCLASS ");
endtask
endclass
program main;
initial
begin
subclass s;
s = new();
s.printf();
end
endprogram
RESULT:
program inhe_31;
Extended obj;
initial
begin
obj = new();
for(int i=0 ; i < 100 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULT:
RESULT
3
4
3
ENCAPSULATION
Encapsulation is a technique for minimizing interdependencies among modules by defining a
strict external communication. This way, internal coding can be changed without affecting the
communication, so long as the new implementation supports the same (or upwards compatible)
external communication.
Encapsulation prevents a program from becoming so interdependent that a small change has
massive ripple effects.
The implementation of an object can be changed without affecting the application that uses it for:
A member identified as local is available only to methods inside the class. Further, these local
members are not visible within subclasses. Of course, nonlocal methods that access local class
properties or methods can be inherited and work properly as methods of the subclass.
RESULT:
Local member 'i' of class 'base' is not accessible from scope 'main'
program main;
initial
begin
base b = new();
b.set(123);
end
endprogram
RESULT
123
In the above example, the varible i is overloaded in subclass with different qualifier.
RESULT
Protected member 'i' of class 'base' is not accessible from scope 'main'
Within a class, a local method or class property of the same class can be referenced, even if it is
in a different instance of the same class.
EXAMPLE
class Packet;
local integer i;
A strict interpretation of encapsulation might say that other.i should not be visible inside of this
packet because it is a local class property being referenced from outside its instance. Within the
same class, however, these references are allowed. In this case, this.i shall be compared to other.i
and the result of the logical comparison returned.
POLYMORPHISM
Polymorphism allows an entity to take a variety of representations. Polymorphism means the
ability to request that the same Operations be performed by a wide range of different types of
things. Effectively, this means that you can ask many different objects to perform the same
action. Override polymorphism is an override of existing code. Subclasses of existing classes are
given a "replacement method" for methods in the superclass. Superclass objects may also use the
replacement methods when dealing with objects of the subtype. The replacement method that a
subclass provides has exactly the same signature as the original method in the superclass.
Polymorphism allows the redefining of methods for derived classes while enforcing a common
interface.To achieve polymorphism the 'virtual' identifier must be used when defining the base
class and method(s) within that class.
EXAMPLE: without virtual
class A ;
task disp ();
$display(" This is class A ");
endtask
endclass
class EA extends A ;
task disp ();
$display(" This is Extended class A ");
endtask
endclass
program main ;
EA my_ea;
A my_a;
initial
begin
my_a = new();
my_a.disp();
my_ea = new();
my_a = my_ea;
my_a.disp();
end
endprogram
RESULTS
This is class A
This is class A
program main ;
EA my_ea;
A my_a;
initial
begin
my_a = new();
my_a.disp();
my_ea = new();
my_a = my_ea;
my_a.disp();
end
endprogram
RESULTS
This is class A
This is Extended class A
Observe the above two outputs. Methods which are declared as virtual are executing the code in
the object which is created.
The methods which are added in the subclasses which are not in the parent class canot be acessed
using the parent class handle. This will result in a compilation error. The compiler check whether
the method is exesting the parent class definition or not.
EXAMPLE:
class A ;
endclass
class EA extends A ;
task disp ();
$display(" This is Extended class A ");
endtask
endclass
program main ;
EA my_ea;
A my_a;
initial
begin
my_ea = new();
my_a = my_ea;
my_ea.disp();
my_a.disp();
end
endprogram
RESULT:
Member disp not found in class A
To access the varible or method which are only in the subclass and not in the parent class, revert
back the object to the subclass handle.
EXAMPLE:
class A ;
endclass
class EA extends A ;
task disp ();
$display(" This is Extended class A ");
endtask
endclass
program main ;
EA my_ea;
A my_a;
initial
begin
my_ea = new();
my_a = my_ea;
just(my_a);
end
endprogram
RESULT
This is Extended class A
Let us see one more example, A parent class is extended and virtual method is redefined in the
subclass as non virtual method. Now if furthur extention is done to the class, then the method is
still considered as virtual method and Polymorphism can be achived still. It is advised to declare
a method as virtual in all its subclass, if it is declared as virtual in baseclass , to avoid confusion
to the end user who is extend the class.
EXAMPLE:
class A ;
virtual task disp ();
$display(" This is class A ");
endtask
endclass
program main ;
EA_2 my_ea;
EA_1 my_a;
initial
begin
my_ea = new();
my_a = my_ea;
my_a.disp();
just(my_a);
end
endprogram
RESULT
ABSTRACT CLASSES
With inheritance we are able to force a subclass to offer the same properties like their
superclasses. Consequently, objects of a subclass behave like objects of their superclasses.
Sometimes it make sense to only describe the properties of a set of objects without knowing the
actual behaviour beforehand
Abstract classes are those which can be used for creation of handles. However their methods and
constructors can be used by the child or extended class. The need for abstract classes is that you
can generalize the super class from which child classes can share its methods. The subclass of an
abstract class which can create an object is called as "concrete class".
class EA extends A ;
task disp ();
$display(" This is Extended class A ");
endtask
endclass
program main ;
EA my_ea;
A my_a;
initial
begin
my_ea = new();
my_a = my_ea;
my_ea.disp();
my_a.disp();
end
endprogram
RESULT
program main ;
A my_a;
initial
begin
my_a = new();
my_a.disp();
end
endprogram
RESULT
Abstract class A cannot be instantiated
Virtual keyword is used to express the fact that derived classes must redefine the properties to
fulfill the desired functionality. Thus from the abstract class point of view, the properties are
only specified but not fully defined. The full definition including the semantics of the properties
must be provided by derived classes.
Definition (Abstract Class) A class A is called abstract class if it is only used as a superclass for
other classes. Class A only specifies properties. It is not used to create objects. Derived classes
must define the properties of A.
PARAMETERISED CLASS
Type Parameterised Class
At the time, when we write down a class definition, we must be able to say that this class should
define a generic type. However, if we don't know with which types the class will be used.
Consequently, we must be able to define the class with help of a placeholder to which we refer as
if it is the type on which the class operates. Thus, the class definition provides us with a template
of an actual class. The actual class definition is created once we declare a particular object. Let's
illustrate this with the following example. Suppose, you want to define a list class which should
be a generic type. Thus, it should be possible to declare list objects for integers, bits,objects or
any other type.
EXAMPLE
class List #(type T = int);
//attributes:
T data_node;
......
......
// methods:
task append(T element);
function T getFirst();
function T getNext();
......
......
endclass
The above template class List looks like any other class definition. However, the first line
declares List to be a template for various types. The identifier T is used as a placeholder for an
actual type. For example, append() takes one element as an argument. The type of this element
will be the data type with which an actual list object is created. For example, we can declare a
list object for "packet" if a definition fot the type "packet" exists:
EXAMPLE
List#(packet) pl; // Object pl is a list of packet
List#(string) sl; // Object sl is a list of strings
The first line declares List#(packet) to be a list for packets. At this time, the compiler uses the
template definition, substitutes every occurrence of T with "packet" and creates an actual class
definition for it. This leads to a class definition similar to the one that follows:
EXAMPLE
class List {
//attributes:
packet data_node;
......
......
// methods:
task append(packet element);
function packet getFirst();
function packet getNext();
......
......
}
This is not exactly, what the compiler generates. The compiler must ensure that we can create
multiple lists for different types at any time. For example, if we need another list for, say
"strings", we can write:
EXAMPLE
List#(packet) pl; // Object pl is a list of packet
List#(string) sl; // Object sl is a list of strings
In both cases the compiler generates an actual class definition. The reason why both do not
conflict by their name is that the compiler generates unique names. However, since this is not
viewable to us, we don't go in more detail here. In any case, if you declare just another list of
"strings", the compiler can figure out if there already is an actual class definition and use it or if
it has to be created. Thus,
EXAMPLE
List#(packet) rcv_pkt; // Object rcv_pkt is a list of packet
List#(packet) sent_pkt; // Object sent_pkt is a list of packet
will create the actual class definition for packet List and will reuse it for another List.
Consequently, both are of the same type. We summarize this in the following definition:
We are able to define template classes with more than one parameter. For example, directories
are collections of objects where each object can be referenced by a key. Of course, a directory
should be able to store any type of object. But there are also various possibilities for keys. For
instance, they might be strings or numbers. Consequently, we would define a template class
Directory which is based on two type parameters, one for the key and one for the stored objects.
Value Parameterised Class
The normal Verilog parameter mechanism is also used to parameterize a class.
EXAMPLE
class Base#(int size = 3);
bit [size:0] a;
task disp();
$display(" Size of the vector a is %d ",$size(a));
endtask
endclass
program main();
initial
begin
Base B1;
Base#(4) B2;
Base#(5) B3;
B1 = new();
B2 = new();
B3 = new();
B1.disp();
B2.disp();
B3.disp();
end
endprogram
RESULT
EXAMPLE
vector #(10) vten; // object with vector of size 10
vector #(.size(2)) vtwo; // object with vector of size 2
typedef vector#(4) Vfour; // Class with vector of size 4
All matching specializations of a particular generic class shall represent the same type. The set of
matching specializations of a generic class is defined by the context of the class declaration.
Because generic classes in a package are visible throughout the system, all matching
specializations of a package generic class are the same type. In other contexts, such as modules
or programs, each instance of the scope containing the generic class declaration creates a unique
generic class, thus, defining a new set of matching specializations.
A generic class is not a type; only a concrete specialization represents a type. In the example
above, the class vector becomes a concrete type only when it has had parameters applied to it, for
example:
typedef vector my_vector; // use default size of 1
EXAMPLE
vector#(6) vx; // use size 6
To avoid having to repeat the specialization either in the declaration or to create parameters of
that type, a typedef should be used:
EXAMPLE
typedef vector#(4) Vfour;
typedef stack#(Vfour) Stack4;
Stack4 s1, s2; // declare objects of type Stack4
Class D1 extends the base class C using the base class¿s default type (bit) parameter.
Class D3 extends the base class C using the parameterized type (P) with which the extended
class is parameterized.
NESTED CLASSES
A nested class is a class whose definition appears inside the definition of another class, as if it
were a member of the other class. The SystemVerilog programming language allows you to
define a class within another class.
EXAMPLE
class StringList;
endclass
class StringTree;
endclass
// StringList::Node is different from StringTree::Node
Nesting allows hiding of local names and local allocation of resources. This is often desirable
when a new type is needed as part of the implementation of a class. Declaring types within a
class helps prevent name collisions and the cluttering of the outer scope with symbols that are
used only by that class. Type declarations nested inside a class scope are public and can be
accessed outside the class
It is a way of logically grouping classes that are only used in one place.
It increases encapsulation.
Nested classes can lead to more readable and maintainable code.
Logical grouping of classes : If a class is useful to only one other class, then it is logical to
embed it in that class and keep the two together. Nesting such "helper classes" makes their
package more streamlined.
Increased encapsulation : Consider two top-level classes, A and B, where B needs access to
members of A that would otherwise be declared private. By hiding class B within class A, A's
members can be declared private and B can access them. In addition, B itself can be hidden from
the outside world.
More readable, maintainable code:Nesting small classes within top-level classes places the code
closer to where it is used.
CONSTANT
SystemVerilog adds another form of a local constant, const. A const form of constant differs
from a localparam constant in that the localparam must be set during elaboration, whereas a
const can be set during simulation, such as in an automatic task.
Constant Class
An instance of a class (an object handle) can also be declared with the const keyword.
EXAMPLE
const class_name object = new(3,3,4);
In other words, the object acts like a variable that cannot be written. The arguments to the new
method must be constant expressions. The members of the object can be written (except for those
members that are declared const).
Global Constant
Global constant class properties include an initial value as part of their declaration. They are
similar to other const variables in that they cannot be assigned a value anywhere other than in the
declaration.
EXAMPLE
class Jumbo_Packet;
const int max_size = 9 * 1024; // global constant
byte payload [];
program main ;
A my_a;
initial
begin
my_a = new();
my_a.i = 55;
end
endprogram
RESULT
EXAMPLE
class A ;
const int i;
function new();
i = 20;
endfunction
endclass
program main ;
A my_a;
initial
begin
my_a = new();
$display(my_a.i);
end
endprogram
RESULT
20
EXAMPLE: error : assignment done twice
class A ;
const int i;
function new();
i = 20;
i++;
endfunction
endclass
RESULT
task set();
i = 20;
endtask
endclass
RESULT
Instance constants assignment allowed only in class constructor.
Typically, global constants are also declared static because they are the same for all instances of
the class. However, an instance constant cannot be declared static because that would disallow
all assignments in the constructor
STATIC
Static Class Properties
A static property is a class variable that is associated with the class, rather than with an instance
of the class (a.k.a., an object). This means that when it is changed, its change is reflected in all
instances of the class. Static properties are declared with the static keyword. If you need to
access a static property inside a class, you can also use the magic keywords "this" and "super",
which resolve to the current class and the parent of the current class, respectively. Using "this"
and "super" allows you to avoid having to explicitly reference the class by name.
EXAMPLE: Using object name
class A ;
static int i;
endclass
program main ;
A obj_1;
A obj_2;
initial
begin
obj_1 = new();
obj_2 = new();
obj_1.i = 123;
$display(obj_2.i);
end
endprogram
RESULT 123
The static class properties can be accessed using class name.
program main ;
A obj_1;
initial
begin
obj_1 = new();
obj_1.i = 123;
$display(A::i);
end
endprogram
RESULT
123
The static class properties can be used without creating an object of that type.
EXAMPLE: without creating object
class A ;
static int i;
endclass
program main ;
A obj_1;
A obj_2;
initial
begin
obj_1.i = 123;
$display(obj_2.i);
end
endprogram
RESULT
123
EXAMPLE: using the object name, without creating object
class A ;
static int i;
endclass
program main ;
A obj_1;
initial
begin
obj_1.i = 123;
$display(A::i);
end
endprogram
RESULT
123
Static Methods
Methods can be declared as static. A static method is subject to all the class scoping and access
rules, but behaves like a regular subroutine that can be called outside the class.
EXAMPLE
class A ;
static task incr();
int j; //automatic variable
j++;
$display("J is %d",j);
endtask
endclass
program main ;
A obj_1;
A obj_2;
initial
begin
$display("Static task - static task with automatic variables");
obj_1 = new();
obj_2 = new();
obj_1.incr();
obj_2.incr();
obj_1.incr();
obj_2.incr();
obj_1.incr();
$display("Static task - Each call to task will create a separate copy of 'j' and increment it");
end
endprogram
RESULT
Static task - static task with automatic variables
J is 1
J is 1
J is 1
J is 1
J is 1
Static task - Each call to task will create a separate copy of 'j' and increment it
A static method has no access to nonstatic members (class properties or methods), but it can
directly access static class properties or call static methods of the same class. Access to nonstatic
members or to the special this handle within the body of a static method is illegal and results in a
compiler error.
EXAMPLE
class A ;
int j;
program main ;
A obj_1;
A obj_2;
initial
begin
obj_1 = new();
obj_2 = new();
obj_1.incr();
obj_2.incr();
end
endprogram
RESULT
A static method has no access to nonstatic members (class properties or methods).
Static methods cannot be virtual.
EXAMPLE
class A ;
int j;
program main;
initial
A.who();
endprogram
RESULT
Im static method.
The static methods can be used without creating an object of that type.
program main;
A a;
initial
a.who();
endprogram
RESULT
Im static method.
Static Lifetime Method.
By default, class methods have automatic lifetime for their arguments and variables.
All variables of a static lifetime method shall be static in that there shall be a single variable
corresponding to each declared local variable in a class , regardless of the number of concurrent
activations of the method.
EXAMPLE
class A ;
task static incr();
int i; //static variable
$display(" i is %d ",i);
i++;
endtask
endclass
program main;
A a;
A b;
initial
begin
$display("Static lifetime - non static task with static variables");
a = new();
b = new();
a.incr();
b.incr();
a.incr();
b.incr();
a.incr();
$display("Static lifetime - Each call to task will use a single value of 'j' and increment it");
end
endprogram
RESULT
Static lifetime - non static task with static variables
i is 0
i is 1
i is 2
i is 3
i is 4
Static lifetime - Each call to task will use a single value of 'j' and increment it
Verilog-2001 allows tasks to be declared as automatic, so that all formal arguments and local
variables are stored on the stack. SystemVerilog extends this capability by allowing specific
formal arguments and local variables to be declared as automatic within a static task, or by
declaring specific formal arguments and local variables as static within an automatic task.
By default, class methods have automatic lifetime for their arguments and variables.
EXAMPLE
class packet;
static int id;
//----static task using automatic fields ---//
static task packet_id();
int count; // by default static task fields are automatic
id=count; // writing in to static variable
$display("id=%d count=%d",id,count);
count++;
endtask
function new();
int pckt_type;
pckt_type++;
$display("pckt_type=%d",pckt_type);
endfunction
endclass
program stic_1;
packet jumbo,pause,taggd;
initial
begin
jumbo=new();
jumbo.packt_id();
pause=new();
pause.packt_id();
taggd=new();
taggd.packt_id();
end
endprogram
RESULTS
pckt_type= 1; id= 0; count= 0
pckt_type= 1; id= 0 ; count= 0
pckt_type= 1; id= 0; count= 0
SystemVerilog allows specific formal arguments and local variables to be declared as automatic
within a static task, or by declaring specific formal arguments and local variables as static within
an automatic task.
EXAMPLE
class packet;
static int id,pckt_type;
//---static task with static field----//
static task packt_id();
static int count; //explicit declaration of fields as static
id=count; //writing in to static variable
$display("id=%d count=%d",id,count);
count++;
endtask
function new();
pckt_type++;
$display("pckt_type=%d",pckt_type);
endfunction
endclass
program stic_2;
packet jumbo,pause,taggd;
initial
begin
jumbo=new();
jumbo.packt_id();
pause=new();
pause.packt_id();
taggd=new();
taggd.packt_id();
end
endprogram
RESULTS
pckt_type= 1; id= 0 count= 0;
pckt_type= 2; id= 1 count= 1;
pckt_type= 3 ; id= 2 count= 2;
CASTING
It is always legal to assign a subclass variable to a variable of a class higher in the inheritance
tree.
EXAMPLE
class parent;
int i = 10;
endclass
function new();
j = super.i;
$display("J is %d",j);
endfunction
endclass
program main;
initial
begin
subclass s;
s = new();
end
endprogram
RESULT
J is 10
It is never legal to directly assign a superclass variable to a variable of one of its subclasses.
However, it is legal to assign a superclass handle to a subclass variable if the superclass handle
refers to an object of the given subclass.
SystemVerilog provides the $cast system task to assign values to variables that might not
ordinarily be valid because of differing data type. To check whether the assignment is legal, the
dynamic cast function $cast() is used . The syntax for $cast() is as follows:
When called as a task, $cast attempts to assign the source expression to the destination variable.
If the assignment is invalid, a run-time error occurs, and the destination variable is left
unchanged.
EXAMPLE : $cast as task
class B;
virtual task print();
$display(" CLASS B ");
endtask
endclass
program main;
initial
begin
B b;
E_1 e1;
E_2 e2;
e1 = new();
$cast(b,e1);
b.print();
end
endprogram
RESULT
CLASS E_1
EXAMPLE : $cast as task with error
class B;
virtual task print();
$display(" CLASS B ");
endtask
endclass
program main;
initial
begin
B b;
E_1 e1;
E_2 e2;
e1 = new();
$cast(e2,e1);
end
endprogram
RESULT
program main;
initial
begin
B b;
E_1 e1;
E_2 e2;
e1 = new();
//calling $cast like a task
//Return value is not considered
$cast(b,e1);
which_class(b);
e2 = new();
//calling $cast like a task
//Return value is not considered
$cast(b,e2);
which_class(b);
end
endprogram
Assignment of Extended class object to Base class object is allowed. It is Illegal to assign Base
class object to Extended class.
EXAMPLE
class Base;
endclass
class Exten extends Base;
endclass
program main;
initial
begin
Base B;
Exten E;
B = new();
if(!$cast(E,B))
$display(" Base class object B canot be assigned to Extended class Handle.");
// Deallocate object B
B = null;
E = new();
if(!$cast(B,E))
$display(" Extended class object E canot be assigned to Base class Handle.");
end
endprogram
RESULT
Base class object B canot be assigned to Extended class Handle.
COPY
The terms "deep copy" and "shallow copy" refer to the way objects are
copied, for example, during the invocation of a copy constructor or assignment operator.
EXAMPLE:
class B;
int i;
endclass
program main;
initial
begin
B b1;
B b2;
b1 = new();
b1.i = 123;
b2 = b1;
$display( b2.i );
end
endprogram
RESULTS:
123
In the above example, both objects are pointing to same memory. The properties did not get
copied. Only the handle is copied.
Shallow Copy
A shallow copy of an object copies all of the member field values.
EXAMPLE:
class B;
int i;
endclass
program main;
initial
begin
B b1;
B b2;
b1 = new();
b1.i = 123;
b2 = new b1;
b2.i = 321;
$display( b1.i );
$display( b2.i );
end
endprogram
RESULTS:
123
321
This works well if the fields are values, but may not be what you want for fields that point to
dynamically allocated memory. The pointer will be copied. but the memory it points to will not
be copied -- the field in both the original object and the copy will then point to the same
dynamically allocated memory, which is not usually what you want. The assignment operator
make shallow copies.
EXAMPLE:
class A;
int i;
endclass
class B;
A a;
endclass
program main;
initial
begin
B b1;
B b2;
b1 = new();
b1.a = new();
b1.a.i = 123;
b2 = new b1;
$display( b1.a.i );
$display( b2.a.i );
b1.a.i = 321;
$display( b1.a.i );
$display( b2.a.i );
end
endprogram
RESULT
123
123
321
321
In the above example, the varible i is changed to which is inside the object of . This changes in
seen in also because both the objects are pointing to same memory location.
Deep Copy
A deep copy copies all fields, and makes copies of dynamically allocated memory pointed to by
the fields. To make a deep copy, you must write a copy constructor and overload the assignment
operator, otherwise the copy will point to the original, with disasterous consequences.
EXAMPLE:
class A;
int i;
endclass
class B;
A a;
endclass
program main;
initial
begin
B b1;
B b2;
b1 = new();
b1.a = new();
b1.a.i = 123;
b2 = new b1;
b2.copy(b1.a);
$display( b1.a.i );
$display( b2.a.i );
b1.a.i = 321;
$display( b1.a.i );
$display( b2.a.i );
end
endprogram
RESULTS:
123
123
321
123
Clone
A clone method returns a new object whose initial state is a copy of the current state of the object
on which clone was invoked. Subsequent changes to the clone will not affect the state of the
original. Copying is usually performed by a clone() method method of a class which is user
defined. This method usually, in turn, calls the clone() method of its parent class to obtain a
copy, and then does any custom copying procedures. Eventually this gets to the clone() method
of Object (the uppermost class), which creates a new instance of the same class as the object and
copies all the fields to the new instance (a "shallow copy"). After obtaining a copy from the
parent class, a class's own clone() method may then provide custom cloning capability, like deep
copying (i.e. duplicate some of the structures referred to by the object) .
One disadvantage is that the return type of clone() is Object, and needs to be explicitly cast back
into the appropriate type (technically a custom clone() method could return another type of
object; but that is generally inadvisable).
One advantage of using clone() is that since it is an overridable method, we can call clone() on
any object, and it will use the clone() method of its actual class, without the calling code needing
to know what that class is (which would be necessary with a copy constructor).
EXAMPLE:
????
program main;
initial
begin
Base b = new;
int bin = 123;
b.print( Base::bin, bin ); // Base::bin and bin are different
end
endprogram
RESULT:
Enum is bin
Val is 123
In addition, to disambiguating class scope identifiers, the :: operator also allows access to static
members (class properties and methods) from outside the class,
EXAMPLE:
class Base;
typedef enum {bin,oct,dec,hex} radix;
task print();
$display(" Ext classs :: enum values %d %d %d %d ",bin,oct,dec,hex);
$display(" Base classs :: enum values %d %d %d %d
",Base::bin,Base::oct,Base::dec,Base::hex);
endtask
endclass
program main;
initial
begin
Ext e;
e = new();
e.print();
end
endprogram
RESULT:
Ext classs :: enum values 2 3 0 1
Base classs :: enum values 0 1 2 3
In SystemVerilog, the class scope resolution operator applies to all static elements of a class:
static class properties, static methods, typedefs, enumerations, structures, unions, and nested
class declarations. Class scope resolved expressions can be read (in expressions), written (in
assignments or subroutines calls), or triggered off (in event expressions). They can also be used
as the name of a type or a method call.
The class scope resolution operator enables the following:
Access to static public members (methods and class properties) from outside the class
hierarchy.
Access to public or protected class members of a superclass from within the derived classes.
Access to type declarations and enumeration named constants declared inside the class from
outside the class hierarchy or from within derived classes.
NULL
The Null is perhaps the most "intelligent" pattern of all. It knows exactly what to do all the time,
every time. Its does nothing. The Null is somewhat difficult to describe, since it resides in an
abstract hierarchy tree, having no particular place at all, but occupying many roles. It is
somewhat like the mathematical concept of zero: it is a placeholder, but is in itself nothing, and
has no value. Null Object is a behavioral pattern designed to act as a default value of an object in
most OOPs tools. These references need to be checked to ensure they are not null before
invoking any methods, because one can't invoke anything on a null reference; this tends to make
code less readable. If you forgot to creat an object ane passed it to method, where the method has
some operation on the object, the simulation fails. So , If the method is expecting an object ,
then check weathe the object is created or not else take nessesary action. The advantage of this
approach over a working default implementation is that a Null Object is very predictable and has
no side effects.
EXAMPLE
class B;
task printf();
$display(" Hi ");
endtask
endclass
program main;
initial
begin
B b;
print(b);
end
endprogram
EXTERNAL DECLARATION
Class methods and Constraints can be defined in the following places:
inside a class.
outside a class in the same file.
outside a class in a separate file.
The process of declaring an out of block method involves:
declaring the method prototype or constraint within the class declaration with extern qualifier.
declaring the full method or constraint outside the class body.
The extern qualifier indicates that the body of the method (its implementation) or constraint
block is to be found outside the declaration.
NOTE : class scope resolution operator :: should be used while defining.
EXAMPLE:
class B;
extern task printf();
endclass
task B::printf();
$display(" Hi ");
endtask
program main;
initial
begin
B b;
b = new();
b.printf();
end
endprogram
RESULT:
Hi
CLASSES AND STRUCTURES
class differs from struct in three fundamental ways:
SystemVerilog structs are strictly static objects; they are created either in a static memory
location (global or module scope) or on the stack of an automatic task. Conversely,
SystemVerilog objects (i.e., class instances) are exclusively dynamic; their declaration does not
create the object. Creating an object is done by calling new.
SystemVerilog objects are implemented using handles, thereby providing C-like pointer
functionality. But, SystemVerilog disallows casting handles onto other data types; thus, unlike C,
SystemVerilog handles are guaranteed to be safe.
SystemVerilog objects form the basis of an Object-Oriented data abstraction that provides true
polymorphism. Class inheritance, abstract classes, and dynamic casting are powerful
mechanisms that go way beyond the mere encapsulation mechanism provided by structs.
TYPEDEF CLASS
Forward Reference
A forward declaration is a declaration of a object which the programmer has not yet given a
complete definition. The term forward reference is sometimes used as a synonym of forward
declaration. However, more often it is taken to refer to the actual use of an entity before any
declaration. The SystemVerilog language supports the typedef class construct for forward
referencing of a class declaration. This allows for the compiler to read a file from beginning to
end without concern for the positioning of the class declaration.
EXAMPLE:
class Y ;
X x; // refers to Class X, which is not yet defined
endclass
class X;
int i;
endclass
RESULT
Error : Class X is not defined
When the compiler encounters the handle x of class type X referred to in class Y, it does not yet
know the definition for class X since it is later in the file. Thus, compilation fails.
To rectify this situation, typedef is used to forward reference the class declaration.
EXAMPLE:
typedef class X;
class Y ;
X x; // refers to Class X, which is not yet defined
endclass
class X;
int i;
endclass
The typedef of class X allows the compiler to process Y before X is fully defined. Note that
typedef cannot be used to forward reference a class definition in another file. This must be done
using the inclusion of a header file.
Circular Dependency
A Circular dependency is a situation which can occur in programming languages wherein the
definition of an object includes the object itself. One famous example is Linked List.
EXAMPLE:
class Y ;
int i;
Y y; // refers to Class Y, which is not yet defined
endclass
As you seen, there is a compilation error. To avoid this situation, typedef is used to forward
reference the class declaration and this circular dependency problem can be avoided.
PURE
As we have already seen in the previous topics , a virtual method may or may not be overridden
in the derived lasses. It means, it is not necessary for a derived class to override a virtual method.
But there are times when a base class is not able to define anything meaningful for the virtual
method in that case every derived class must provide its own definition of the that method.
A pure virtual method is a virtual method that you want to force derived classes to override. If a
class has any unoverridden pure virtuals, it is an "abstract class" and you can't create objects of
that type.
" pure virtual function " or " pure virtual task " declaration is supposed to represent the fact that
the method has no implementation.
There are two major differences between a virtual and a pure virtual function, these are below:
There CAN'T be a definition of the pure virtual function in the base class.
There MUST be a definition of the pure virtual function in the derived class.
EXAMPLE:
class Base;
pure virtual task disp();
end class
program main
initial
begin
Base B;
B = new();
B.disp();
end
endprogram
RESULT
Error: pure virtual task disp(); must be overridden in derived class
OTHER OOPS FEATURES
Multiple inheritence and Function overloading and the OOPs features which are not supported
by System Verilog.
Multiple Inheritence
Multiple inheritance refers to a feature of some object-oriented programming languages in which
a class can inherit behaviors and features from more than one superclass. This contrasts with
single inheritance, where a class may inherit from at most one superclass. SystemC supports
multiple inheritance, SystemVerilog supports only single inheritance.
Multiple inheritance allows a class to take on functionality from multiple other classes, such as
allowing a class named D to inherit from a class named A, a class named B, and a class named
C.
EXAMPLE:
class A;
.....
endclass
class B;
......
endclass
class C;
......
endclass
MISC
Always Block In Classes
SystemVerilog doesnot allow to define always block in program block or class, as these are
meant for testbench purpose.
Example to show the implimentation of always block in program block.
EXAMPLE:
program main;
integer a,b;
initial
repeat(4)
begin
#({$random()}%20)
a = $random();
#({$random()}%20)
b = $random();
end
initial
always_task();
task always_task();
fork
forever
begin
@(a,b);
$display(" a is %d : b is %d at %t ",a,b,$time);
end
join_none
endtask
endprogram
RESULT
a is -1064739199 : b is x at 8
a is -1064739199 : b is -1309649309 at 25
a is 1189058957 : b is -1309649309 at 42
a is 1189058957 : b is -1992863214 at 47
a is 114806029 : b is -1992863214 at 48
a is 114806029 : b is 512609597 at 66
a is 1177417612 : b is 512609597 at 75
a is 1177417612 : b is -482925370 at 84
Example to show the implimentation of always block in class.
EXAMPLE
class Base;
integer a,b;
task always_task();
fork
forever
begin
@(a,b);
$display(" a is %d : b is %d at %t ",a,b,$time);
end
join_none
endtask
endclass
program main;
initial
begin
Base obj;
obj = new();
// start the always block.
fork
obj.always_task();
join_none
repeat(4)
begin
#({$random()}%20)
obj.a = $random();
#({$random()}%20)
obj.b = $random();
end
end
endprogram
RESULT
a is -1064739199 : b is x at 8
a is -1064739199 : b is -1309649309 at 25
a is 1189058957 : b is -1309649309 at 42
a is 1189058957 : b is -1992863214 at 47
a is 114806029 : b is -1992863214 at 48
a is 114806029 : b is 512609597 at 66
a is 1177417612 : b is 512609597 at 75
a is 1177417612 : b is -482925370 at 84
CONSTRAINED RANDOM VERIFICATION
Introduction :
Historically,verification engineers used directed test bench to verify the functionality of their
design.Rapid changes have occurred during the past decades in design and verification.High
Level Verification Languages (HVLS) such as e, System c,Vera,SystemVerilog have become a
necessity for verification environments.
Constraint Random stimulus generation is not new. Everybody uses verilog and VHDL at very
low level abstraction for this purpose. HVLS provide constructs to express specification of
stimulus at high level of abstraction and constraint solver generates legal stimulus.
Writing constraints at higher level of absctraction,makes the programming closer to spec.
A constraint language should support:
Expressions to complex scenarios.
Felxibility to control dynamically.
Combinational and sequential constraints.
EXAMPLE:
Combinational constraint :
In ethernet, 13 & 14 th bytes should be equal to payload length.
Sequential constraint:
If request comes, then acknoldegement should be given between 4th to 10th cycles.
This article is about Constrained random verification using SystemVerilog.I tried to explain
every point using examples.
VERILOG CRV
Constrained Random Stimulus Generation In Verilog:
Verilog has system function $random, which can be used to generate random input vectors.
With this approach,we can generate values which we wouldnt have got, if listed manually. In this
topic I would like to discuss what natural things happening behind $random and how we use it in
different manners.
EXAMPLE:
module Tb_mem();
reg clock;
reg read_write;
reg [31:0] data;
reg [31:0] address;
initial
begin
clock = 0;
forever #10 clock = ~clock;
end
initial
begin
repeat(5)@(negedge clock)
begin
read_write = $random ; data = $random; address = $random;
$display($time," read_write = %d ; data = %d ; address = %d;",read_write,data,address);
end
#10 $finish;
end
endmodule
RESULT:
20 read_write = 0 ; data = 3230228097 ; address = 2223298057;
40 read_write = 1 ; data = 112818957 ; address = 1189058957;
60 read_write = 1 ; data = 2302104082 ; address = 15983361;
80 read_write = 1 ; data = 992211318 ; address = 512609597;
100 read_write = 1 ; data = 1177417612 ; address = 2097015289;
$random system function returns a new 32-bit random number each time it is called. The
random number is a signed integer; it can be positive or negative.The following example
demonstrates random generation of signed numbers.
EXAMPLE:
module Tb();
integer address;
initial
begin
repeat(5)
#1 address = $random;
end
initial
$monitor("address = %0d;",address);
endmodule
RESULT:
address = 303379748;
address = -1064739199;
address = -2071669239;
address = -1309649309;
address = 112818957;
We have seen how to generate random numbers.But the numbers randge from - (2**32 -1) to 2
**32. Most of the time, the requirement is dont need this range. For example, take a memory.
The address starts from 0 to some 1k or 1m.Generating a random address which DUT is not
supporting is meaning less. In verilog there are no constructs to constraint randomization.
Following example demonstrated how to generate random number between 0 to 10.Using %
operation,the remainder of any number is always between 0 to 10.
EXAMPLE:
module Tb();
integer add_1;
initial
begin
repeat(5)
begin
#1;
add_1 = $random % 10;
end
end
initial
$monitor("add_1 = %0d",add_1);
endmodule
RESULT:
add_1 = 8;
add_1 = 4294967287;
add_1 = 4294967295;
add_1 = 9;
add_1 = 9;
OOPS!...... The results are not what is expected.The reason is $random generates negative
numbers also. The following example demonstrates proper way of generating a random number
between 0 to 10. Concatenation operator returns only bitvector. Bit vectors are unsigned, so the
results are correct as we expected. Verilog also has $unsigned systemtask to convert signed
numbers to signed number.This can also be used to meet the requirements. The following
example shows the usage of concatenation operator and $unsigned.
EXAMPLE:
module Tb();
integer add_2;
reg [31:0] add_1;
integer add_3;
initial
begin
repeat(5)
begin
#1;
add_1 = $random % 10;
add_2 = {$random} %10 ;
add_3 = $unsigned($random) %10 ;
end
end
initial
$monitor("add_3 = %0d;add_2 = %0d;add_1 = %0d",add_3,add_2,add_1);
endmodule
RESULT:
add_3 = 7;add_2 = 7;add_1 = 8
add_3 = 7;add_2 = 7;add_1 = 4
add_3 = 1;add_2 = 2;add_1 = 4
add_3 = 7;add_2 = 8;add_1 = 9
add_3 = 9;add_2 = 2;add_1 = 9
The above example shows the generation of numbers from 0 to N. Some specification require the
range to start from non Zero number. MIN + {$random} % (MAX - MIN ) will generate random
numbers between MIN and MAX.
EXAMPLE:
module Tb();
integer add;
initial
begin
repeat(5)
begin
#1;
add = 40 + {$random} % (50 - 40) ;
$display("add = %0d",add);
end
end
endmodule
RESULT:
add = 48
add = 47
add = 47
add = 47
add = 47
Now how to generate a random number between two ranges? The number should be between
MIN1 and MAX1 or MIN2 and MAX2. The following example show how to generate this
specification.
EXAMPLE:
module Tb();
integer add;
initial
begin
repeat(5)
begin
#1;
if($random % 2)
add = 40 + {$random} % (50 - 40) ;
else
add = 90 + {$random} % (100 - 90) ;
$display("add = %0d",add);
end
end
endmodule
RESULT:
add = 97
add = 47
add = 47
add = 42
add = 49
All the random numbers generated above are 32 bit vectors, which is not always the same
requirement. For example, to generate a 5 bit and 45 bit vector random number, the following
method can be used.
EXAMPLE:
module Tb();
reg [4:0] add_1;
reg [44:0] add_2;
initial
begin
repeat(5)
begin
add_1 = $random ;
add_2 = {$random,$random};
$display("add_1 = %b,add_2 = %b ",add_1,add_2);
end
end
endmodule
RESULTS:
add_1 = 00100,add_2 = 111101000000110000100100001001101011000001001
add_1 = 00011,add_2 = 110110000110101000110110111111001100110001101
add_1 = 00101,add_2 = 100100001001000000000111100111110001100000001
add_1 = 01101,add_2 = 100010111011000011110100011011100110100111101
add_1 = 01101,add_2 = 101111000110001111100111111011110100111111001
Some protocols require a random number which is multiple to some number. For example,
Ethernet packet is always in multiples of 8bits. Look at the following example. It generates a
random number which is multiple of 3 and 5.
$random * 3 will give random numbers which are multiples of 3. But if the number after
multiplication needs more than 32 bit to reprasent, then the results may be wrong.
EXAMPLE:
module Tb();
integer num_1,num_2,tmp;
initial
begin
repeat(5)
begin
num_1 =( $random / 3)*3;
num_2 =( $random / 5)*5;
$display("num_1 = %d,num_2 = %d",num_1,num_2);
end
end
endmodule
RESULT:
num_1 = 303379746,num_2 = -1064739195
num_1 = -2071669239,num_2 = -1309649305
num_1 = 112818957,num_2 = 1189058955
num_1 = -1295874969,num_2 = -1992863210
num_1 = 15983361,num_2 = 114806025
All the above examples show that the random numbers are integers only. In verilog there is no
special construct to generate a random real number.The following method shows generation of
random real numbers.
EXAMPLE:
module Tb();
integer num_1,num_2,num_3;
real r_num;
initial
begin
repeat(5)
begin
#1;
num_1 = $random;
num_2 = $random;
num_3 = $random;
r_num = num_1 + ((10)**(-(num_2)))*(num_3);
$display("r_num = %e",r_num);
end
end
endmodule
RESULT:
r_num = -2.071669e+03
r_num = 2641.189059e+013
r_num = 976361.598336e+01
r_num = 57645.126096e+02
r_num = 24589.097015e+0
To generate random real number , system function $bitstoreal can also be used.
EXAMPLE:
module Tb();
real r_num;
initial
begin
repeat(5)
begin
#1;
r_num = $bitstoreal({$random,$random});
$display("r_num = %e",r_num);
end
end
endmodule
RESULTS:
r_num = 1.466745e-221
r_num = -6.841798e-287
r_num = 2.874848e-276
r_num = -3.516622e-64
r_num = 4.531144e-304
If you want more control over randomizing real numbers interms of sign, exponential and
mantissa, use $bitstoreal() as shown in example below. For positive numbers use sgn = 0 and for
negative numbers use sgn = 1 .
EXAMPLE:
module Tb();
reg sgn;
reg [10:0] exp;
reg [51:0] man;
real r_num;
initial
begin
repeat(5)
begin
sgn = $random;
exp = $random;
man = $random;
r_num = $bitstoreal({sgn,exp,man});
$display("r_num = %e",r_num);
end
end
endmodule
RESULTS:
r_num = 3.649952e+193
r_num = -1.414950e-73
r_num = -3.910319e-149
r_num = -4.280878e-196
r_num = -4.327791e+273
Some times it is required to generate random numbers without repetition. The random numbers
should be unique. For example,to generate 10 random numbers between 0 to 9 without
repetition, the following logic can be used.
EXAMPLE:
module Tb();
integer num,i,j,index;
integer arr[9:0];
reg ind[9:0];
reg got;
initial
begin
index=0;
for(i=0;i<10;i=i+1)
begin
arr[i] = i;
ind[i] = 1;
end
for(j = 0;j<10 ;j=j+1)
begin
got = 0;
while(got == 0)
begin
index = { $random } % 10;
if(ind[index] == 1)
begin
ind[index] = 0;
got = 1;
num = arr[index];
end
end
$write("| num=%2d |",num);
end
end
endmodule
RESULT:
| num= 8 || num= 7 || num= 5 || num= 2 || num= 1 || num= 9 || num= 6 || num= 4 || num= 0 || num=
3|
Random number returned by $random system function should be deterministic, i.e when ever we
run with simulator it should return values in same sequence. Otherwise the bug found today cant
be found return. For this purpose it has one argument called seed. The seed parameter controls
the numbers that $random returns, such that different seeds generate different random streams.
The seed parameter shall be either a reg, an integer, or a time variable. The seed value should be
assigned to this variable prior to calling $random.
EXAMPLE:
module Tb();
integer num,seed,i,j;
initial
begin
for(j = 0;j<4 ;j=j+1)
begin
seed = j;
$display(" seed is %d",seed);
for(i = 0;i < 10; i=i+1)
begin
num = { $random(seed) } % 10;
$write("| num=%2d |",num);
end
$display(" ");
end
end
endmodule
RESULT:
seed is 0
| num= 8 || num= 7 || num= 7 || num= 7 || num= 7 || num= 7 || num= 5 || num= 2 || num= 1 || num=
9|
seed is 1
| num= 8 || num= 8 || num= 2 || num= 2 || num= 6 || num= 3 || num= 8 || num= 5 || num= 5 || num=
5|
seed is 2
| num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num=
6|
seed is 3
| num= 8 || num= 2 || num= 2 || num= 3 || num= 8 || num= 6 || num= 1 || num= 4 || num= 3 || num=
9|
The $random function has its own implicit variable as seed when user is not giving explicitly
seed. The following example shows that seed = 0 and implicit seed are having same sequence.It
means that the imlicity taken seed is also 0.
EXAMPLE:
module Tb();
integer num,seed,i,j;
initial
begin
seed = 0;
for(j = 0;j<2 ;j=j+1)
begin
if(j ==0)
$display(" seed is %d",seed);
else
$display(" No seed is given ");
for(i = 0;i < 10; i=i+1)
begin
if( j == 0)
num = { $random(seed) } % 10;
else
num = { $random } % 10;
$write("| num=%2d |",num);
end
$display(" ");
end
end
endmodule
RESULT:
seed is 0
| num= 8 || num= 7 || num= 7 || num= 7 || num= 7 || num= 7 || num= 5 || num= 2 || num= 1 || num=
9|
No seed is given
| num= 8 || num= 7 || num= 7 || num= 7 || num= 7 || num= 7 || num= 5 || num= 2 || num= 1 || num=
9|
The system functions shall always return same series of values with same seed. This facilitates
debugging by making the operation of the system repeatable. The argument for the seed
parameter should be an integer variable that is initialized by the user and only updated by the
system function. This ensures the desired distribution is achieved. In the following example,
when ever the seed is changed to 2, the sequence 8-1-0-5-0-8-6-7-11-6......... is followed. Check
out in any tool you will see the same sequence.
EXAMPLE:
module Tb();
integer num,seed,i,j;
initial
begin
for(j = 0;j<4 ;j=j+1)
begin
seed = 2;
$display(" seed is %d",seed);
for(i = 0;i < 10; i=i+1)
begin
num = { $random(seed) } % 10;
$write("| num=%2d |",num);
end
$display(" ");
end
end
endmodule
RESULT:
seed is 2
| num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num=
6|
seed is 2
| num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num=
6|
seed is 2
| num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num=
6|
seed is 2
| num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num=
6|
Seed is inout port. Random number system function returns a random number and also returns a
random number to seed inout argument also. The results of the following example demonstrates
how seed value is getting changed.
EXAMPLE:
module Tb();
integer num,seed,i,j;
initial
begin
seed = 0;
for(j = 0;j<10 ;j=j+1)
begin
num = { $random(seed) } % 10;
$write("| num=%2d |",num);
$display(" seed is %d ",seed);
end
end
endmodule
RESULT:
| num= 8 | seed is -1844104698
| num= 7 | seed is 1082744015
| num= 7 | seed is 75814084
| num= 7 | seed is 837833973
| num= 7 | seed is -2034665166
| num= 7 | seed is -958425333
| num= 5 | seed is 851608272
| num= 2 | seed is 154620049
| num= 1 | seed is -2131500770
| num= 9 | seed is -2032678137
From the above results we can make a table of seed values and return values of $random. If a
seed is taken from table, then rest of the sequence has to follow sequence in table.
Table is as follows for initial seed 0;
| num= 8 | seed is -1844104698
| num= 7 | seed is 1082744015
| num= 7 | seed is 75814084
| num= 7 | seed is 837833973
| num= 7 | seed is -2034665166
| num= 7 | seed is -958425333
| num= 5 | seed is 851608272
| num= 2 | seed is 154620049
| num= 1 | seed is -2131500770
| num= 9 | seed is -2032678137
.
.
.
.
.
table goes on........
In the following example, the seed is 837833973, which is the 4 th seed from the above table.
EXAMPLE:
module Tb();
integer num,seed,i,j;
initial
begin
seed = 837833973;
for(j = 0;j<10 ;j=j+1)
begin
num = { $random(seed) } % 10;
$write("| num=%2d |",num);
$display(" seed is %d ",seed);
end
end
endmodule
RESULTS:
| num= 7 | seed is -2034665166
| num= 7 | seed is -958425333
| num= 5 | seed is 851608272
| num= 2 | seed is 154620049
| num= 1 | seed is -2131500770
| num= 9 | seed is -2032678137
| num= 8 | seed is -1155272804
| num= 7 | seed is -1634874387
| num= 9 | seed is -153856566
| num= 2 | seed is -970066749
From the above example we can come to conclusion that $random is not giving a random
number. It is randomizing seed and returning corresponding number for that seed.
Total possible seed values are 4294967295(32'hFFFF_FFFF). Is it possible for $random to
generate all the seeds? . Lets say, if the seed gets repeated after 10 iterations, then after the 10
iterations, same values are repeated. So $random is circulating inside a chain of 10 numbers.
The following example demonstrates how $random misses many seeds. I tried to display the
seeds between 0 to 20 in the chaining formed by initial seed of 0. Results show that total possible
seeds are 4294967295 , and number of seeds possible in seed chain are 4030768279 , so we are
missing some seeds. Look at the seeds between 0 to 20. Seed == 1 is missing.
EXAMPLE:
module Tb();
integer num,seed,j;
reg [0:31] i;
initial
begin
i = 0;
seed = 1;
while (seed != 0)
begin
if(i == 0)
seed = 0;
i = i + 1;
num = $random(seed);
if(seed < 20 && seed > 0)
$display(" seed is %d after values %d ",seed,i);
end
$display(" seed is zero after this number of random numbers %0d total numbers available are
%d",i,{32'hffff_ffff});
end
endmodule
RESULTS:
seed is 10 after values 93137101
seed is 17 after values 307298440
seed is 2 after values 410139893
seed is 12 after values 483530075
seed is 19 after values 592243262
seed is 3 after values 720224974
seed is 11 after values 1342230278
seed is 15 after values 2032553666
seed is 7 after values 2266624778
seed is 13 after values 2362534380
seed is 5 after values 2512466932
seed is 9 after values 2575033104
seed is 16 after values 2988686279
seed is 4 after values 3173376451
seed is 6 after values 3483433473
seed is 8 after values 3547878575
seed is 14 after values 3663208793
seed is 18 after values 3930700709
seed is zero after this number of random numbers 4030768279 total numbers available are
4294967295
Now I tried to simulate with seed = 1 . It's interisting to know that some how the sequence is able
to enter this chaining which is formed with seed=0 and there is no seed value 1 in this chaining
and my simulation hanged. So aborted the simulation and partial results show that the initial seed
= 1 enters the chaing formed by seed 0.
EXAMPLE:
module Tb();
integer num,seed,j;
reg [0:31] i;
initial
begin
i = 0;
seed = 0;
while (seed != 1)
begin
if(i == 0)
seed = 1;
i = i + 1;
num = $random(seed);
if(seed < 20 && seed > 0)
$display(" seed is %d after values %d ",seed,i);
end
$display(" seed is one after this number of random numbers %0d total numbers available are
%d",i,{32'hffff_ffff});
end
endmodule
RESULTS:
seed is 10 after values 357336117
seed is 17 after values 571497456
seed is 2 after values 674338909
seed is 12 after values 747729091
seed is 19 after values 856442278
seed is 3 after values 984423990
seed is 11 after values 1606429294
seed is 15 after values 2296752682
seed is 7 after values 2530823794
seed is 13 after values 2626733396
seed is 5 after values 2776665948
seed is 9 after values 2839232120
seed is 16 after values 3252885295
seed is 4 after values 3437575467
seed is 6 after values 3747632489
seed is 8 after values 3812077591
seed is 14 after values 3927407809
seed is 18 after values 4194899725
seed is 10 after values 357336117
seed is 17 after values 571497456
seed is 2 after values 674338909
seed is 12 after values 747729091
seed is 19 after values 856442278
seed is 3 after values 984423990
Verilog also has other system functions to generate random numbers. Each of these functions
returns a pseudo-random number whose characteristics are described by the function name.
Following are the Verilog random number genrator system functions:
$random
$dist_chi_square
$dist_erlang
$dist_exponential
$dist_normal
$dist_poisson
$dist_t
$dist_uniform
All parameters to the system functions are integer values. For the exponential, poisson, chi-
square ,t and erlang functions the parameters mean, degree of freedom, and k_stage must be
greater than 0.
$dist_uniform(seed, min, max) is similar to min + {$random(seed)}%(max-min+1), the
difference is that in $dist_uniform the distribution is uniform. $dist_uniform returns a number
between min and max. In the $dist_uniform function, the start and end parameters are integer
inputs that bound the values returned. The start value should be smaller than the end value.
The mean parameter used by $dist_normal, $dist_exponential, $dist_poisson and $dist_erlang is
an integer input that causes the average value returned by the function to approach the value
specified. The standard deviation parameter used with the $dist_normal function is an integer
input that helps determine the shape of the density function. Larger numbers for standard
deviation spread the returned values over a wider range.
The degree of freedom parameter used with the $dist_chi_square and $dist_t functions is an
integer input that helps determine the shape of the density function. Larger numbers spread the
returned values over a wider range.
EXAMPLE:
module Tb();
integer num_1,num_2,seed;
initial
begin
seed = 10;
repeat(5)
begin
#1;
num_1 = $dist_uniform(seed,20,25);
num_2 = $dist_uniform(seed,50,55);
$display("num_1 = %d,num_2 = %d",num_1,num_2);
end
end
endmodule
RESULTS:
num_1 = 20,num_2 = 50
num_1 = 23,num_2 = 55
num_1 = 22,num_2 = 54
num_1 = 25,num_2 = 51
num_1 = 23,num_2 = 55
As i discussed $random randomizes its seed, Lets see whether $dist_uniform is also doing the
same.
EXAMPLE:
module Tb();
integer num_1,num_2,seedd,seedr;
initial
begin
seedd = 10;
seedr = 10;
repeat(5)
begin
#1;
num_1 = $dist_uniform(seedd,20,25);
num_2 = 20 + ({$random(seedr)} % 6);
$display("num_1 = %d,num_2 = %d,seedd = %d seedr = %d",num_1,num_2,seedd,seedr);
end
end
endmodule
RESULTS:
num_1 = 20,num_2 = 22,seedd = 690691 seedr = 690691
num_1 = 20,num_2 = 20,seedd = 460696424 seedr = 460696424
num_1 = 23,num_2 = 22,seedd = -1571386807 seedr = -1571386807
num_1 = 25,num_2 = 21,seedd = -291802762 seedr = -291802762
num_1 = 22,num_2 = 23,seedd = 1756551551 seedr = 1756551551
Look at the results... Its interesting to note that $random and $dist_uniform have same seed
sequence flow also.
RESULTS:
num_2 = 3012;num_3 = 2964;num_4 = 3065;num_11 = 3001;num_12 = 2964;num_13 = 2975
The distribution is uniform for system function $random. Suppose if the requirement is to
generate random numbers for more than one variable, and all the variables should have uniform
distribution, then use different seeds for each variable. Other wise distribution is distributed on
all the variables as overall variables which mightnot be our requirement.. But for lower bits, the
distribution is same as shown in example.
EXAMPLE:
module Tb();
integer seed;
reg [1:0] var_1,var_2,var3,var4;
integer num_2,num_3,num_1,num_0;
integer cou_2,cou_3,cou_1,cou_0;
initial
begin
seed = 10;
num_2 = 0;num_3= 0;num_1= 0;num_0= 0;
cou_2= 0;cou_3= 0;cou_1= 0;cou_0= 0;
repeat(40000)
begin
var_1 = $random;
var3 = $random;
var4 = $random;
var_2 = $random;
if(var_1 == 0 )
num_0 = num_0 + 1;
if(var_1 == 1 )
num_1 = num_1 + 1;
if(var_1 == 2 )
num_2 = num_2 + 1;
if(var_1 == 3 )
num_3 = num_3 + 1;
if(var_2 == 0 )
cou_0 = cou_0 + 1;
if(var_2 == 1 )
cou_1 = cou_1 + 1;
if(var_2 == 2 )
cou_2 = cou_2 + 1;
if(var_2 == 3 )
cou_3 = cou_3 + 1;
end
$display("num_2 = %0d;num_3= %0d;num_1= %0d;num_0=
%0d;",num_2,num_3,num_1,num_0);
$display("cou_2= %0d;cou_3= %0d;cou_1= %0d;cou_0=
%0d;",cou_2,cou_3,cou_1,cou_0);
end
endmodule
RESULTS:
num_2 = 9984;num_3= 10059;num_1= 10002;num_0= 9955;
cou_2= 10060;cou_3= 9934;cou_1= 10072;cou_0= 9934;
Use system time as seed, so the same TB simulated at different times have different random
sequences and there is more probability of finding bugs. The following is C code useful in PLI to
get system time in to verilog.
#include <stdio.h>
#include <time.h>
char *get_time_string(int mode24);
int get_systime() {
time_t seconds;
seconds = time (NULL);
return seconds;
}
In Verilog 1995 standard every simulator has its own random number generation algorithm. But
in Verilog 2001 a standard is made that every simulator has to follow same algorithm. So the
same random number sequence can seen on different simulators for same seed.
Don't expect that the same sequence is generated on all the simulators. They are only following
same algorithm. The reason is race condition. Look at following example, both the
statements_1 and statement_2 are scheduled to execute at same simulation time. The order of
execution is not not known. Some simulators take statement_1 as the first statement to
execute and some other statement_2. If theTB is built without any race condition
to $random function calls, then the same random sequence can be generated on different
simulators and a testbench without a racecondition on $random calls is not easy to build.
Look at the following 2 examples. I just changed the order of statements, the results are
reversed.
@edes
EXAMPLE:new
module Tb();
initial
$display("staement 2 :::%d",$random);
initial
$display("staement 1 :::%d",$random);
endmodule
staement 2 ::: 303379748
staement 1 :::-1064739199
EXAMPLE:
module Tb();
initial
$display("staement 1 :::%d",$random);
initial
$display("staement 2 :::%d",$random);
endmodule
staement 1 ::: 303379748
staement 2 :::-1064739199
SYSTEMVERILOG CRV
Systemverilog Constraint Random Stmulus Generaion :
We have seen how to get random values and constrain them. These constraints are at very low
level of abstraction. Todays verification needs a better way to describ the constraints.
SystemVerilog has randomization constructs to support todays verification needs.
Following are the features of SystemVerilog which support Constraint Random Verification
(CRV) :
1) Constraints : Purely random stimulus takes too long to generate interesting senarious.
Specify the interesting subset of all possible stimulus with constraint blocks. These are features
provided by SystemVerilog for constraining randomness. Random variable generated in verilog
Boolean expressions, foreach (for constraining elements of array), set membership, inline
constraints, rand case, rand sequence, Conditional constraints and implication constraints.
Randomizing Objects
Generating random stimulus within objects :
SystemVerilog allows object-oriented programiming for random stimulus generation, subjected
to specified constraints. During randomization, variables declared as rand or randc inside class
are only considered for randomization. Built-in randomize() method is called to generate new
random values for the declared random variables.
EXAMPLE:
program Simple_pro_5;
class Simple;
rand integer Var;
endclass
Simple obj;
initial
begin
obj = new();
repeat(5)
if(obj.randomize())
$display(" Randomization successful : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# Randomization sucsessfull : Var = -82993358
# Randomization sucsessfull : Var = -112342886
# Randomization sucsessfull : Var = -867551972
# Randomization sucsessfull : Var = -34537722
# Randomization sucsessfull : Var = 1977312553
Random Unpacked Structs:
SystemVerilog allows unpackedstructs to be declared as rand for randomization. Only members
of struct which are declared as rand or randc are only randomized. randc is not allowed on
unpacked structs. If Struct is not declared as rand, solver considers it as state variable.
EXAMPLE:
class Rand_struct;
typedef struct {
randc int Var1;
int Var2;
} Struct_t;
rand Struct_t Str; // To randomize Var1 and Struct_t type has to declared as rand
endclass
program stru_rand_6;
Rand_struct RS ;
initial
begin
RS = new();
repeat(10)
if(RS.randomize())
$display(" Var1 : %d",RS.Str.Var1);
end
endprogram
RESULTS:
# Var1 : -430761355
# Var1 : 424439941
# Var1 : -1129955555
# Var1 : 1781908941
# Var1 : -752252755
# Var1 : 922477361
# Var1 : -2115981855
# Var1 : 1551031961
# Var1 : -91393015
# Var1 : 262093271
Simillar to struct, the same can be achived using class by calling the randomize() function on the
object, which is created by using class.
EXAMPLE:
program class_rand_7;
class Class_t;
rand int Var1;
int Var2;
endclass
class Rand_class;
rand Class_t Cla; // To randomize Var1,Class_t type has to declared as rand
function new();
Cla = new();
endfunction
endclass
Rand_class RC = new();
initial
repeat(10)
if(RC.randomize())
$display(" Var1 : %0d Var2 : %0d",RC.Cla.Var1,RC.Cla.Var2);
endprogram
RESULTS:
# Var1 : 733126180 Var2 : 0
# Var1 : -119008195 Var2 : 0
# Var1 : 342785185 Var2 : 0
# Var1 : 679818185 Var2 : 0
# Var1 : -717162992 Var2 : 0
# Var1 : 664520634 Var2 : 0
# Var1 : -1944980214 Var2 : 0
# Var1 : -1350759145 Var2 : 0
# Var1 : -1374963034 Var2 : 0
# Var1 : -462078992 Var2 : 0
SystemVerilog structs are static objects, where as class instances are dynamic objects, declaring
a class instance does not allocate memory space for object. Calling built in new() function
creates memory for object. Class have built in functions and tasks, where as struct dont, this
speeds up simulation time if structs are used. Check your data structure, if they need simple
encapsulation use struct otherwise if they need object oriented mechanisms then choose class.
Rand Case :
You can use randcase to make a weighted choice between different items, without having to
create a class and instance. An items weight divided by the sum of all weights gives the
probability of taking that branch. More details are discussed in the following units.
Rand Sequence :
SystemVerilog provides randsequnce to generate random sequencess of operation. It will be
useful for randomly generating structured sequences of stimulus such as instructions or network
traffic patterns.
RANDOMIZING OBJECTS
Generating Random Stimulus Within Class :
SystemVerilog features which support Constraint random generation inside objects are :
RANDOM VARIABLES
Random Varible Declaration:
Variables declared as rand or randc are only randomized due to call of randomize() function. All
other varibles are considered as state variables.
EXAMPLE:
class ex_8;
rand [3:0] var1;
randc [3:0] var2;
rand integer var3;
endclass
Fixed arrays, dynamic arrays, associative arrays and queues can be declared as rand or randc. All
their elements are treated as random. Individual array elements can also be constrained,in this
case, index expression must be constant. For dynamic arrays, the size of the array length can be
constrained. Non integer data types like shortreal, real and realtime are not allowed for random
variable declaration.
Rand Modifier :
Variables declared with rand keyword are standard random variables. When there are no other
control on distrubution, these variables are uniformly distributed across valid values.
EXAMPLE:
class rand_cl;
rand bit [0:2] Var;
constraint limit_c { Var < 4;}
endclass
program rand_p_9;
rand_cl obj;
integer count_0, count_1, count_2, count_3;
initial
begin
obj = new();
count_0 = 0;count_1 = 0;count_2 = 0;count_3 = 0;
repeat(100000)
begin
void'(obj.randomize());
if( obj.Var == 0) count_0 ++;
else if( obj.Var == 1) count_1 ++;
else if( obj.Var == 2) count_2 ++;
else if( obj.Var == 3) count_3 ++;
end
$display(" count_0 = %0d , count_1 = %0d, count_2 = %0d, count_3 = %0d
",count_0, count_1, count_2, count_3);
end
endprogram
RESULTS:
count_0 = 25046 , count_1 = 24940, count_2 = 24969, count_3 = 25045
Simulation results show that the rand variable is distrubuted uniformly.
Randc Modifier :
Variables declared as randc are random cyclic that randomly iterates over all the values in the
range and no value is repeated with in an iteration until every possible value has been assigned.
But Iteration sequences are won't be same. Bit and enumerated types can be randc variables. To
reduce memory requirements, implementations can impose a limit on maximum size of a randc
variable, it should be not be more than 8 bits.
EXAMPLE:
class rand_c;
randc bit [1:0] Var;
endclass
program rand_cp_10;
rand_c obj=new();
initial
for(int i=0;i<20;i++)
begin
void'(obj.randomize());
$write("%0d_",obj.Var);
if(i%4==3)
$display("");
end
endprogram
RESULTS:
# 0_3_1_2_
# 3_0_2_1_
# 0_3_1_2_
# 0_1_2_3_
# 3_0_2_1_
The permutation sequence for any given randc variable is recomputed whenever the constraints
changes on that variable or when none of the remaining values in the permutation can satisfy the
constraints.
EXAMPLE:
class rand_c;
randc bit [2:0] Var;
integer MIN = 4;
constraint C { Var < MIN ;}
endclass
program rand_cp_11;
rand_c obj=new();
initial
for(int i=0;i<20;i++)
begin
obj.MIN = 4;
if(i>12)
obj.MIN=7;
void'(obj.randomize());
if(i==12)
$display(" CONSTRAINT CHANGED ");
$write("%0d_",obj.Var);
if((i%4==3))
$display("");
end
endprogram
RESULTS:
0_2_3_1_
0_1_3_2_
3_2_0_1_
CONSTRAINT CHANGED
0_1_4_2_
6_5_3_0_
Permutation sequence is computed on every call of new() function. So if randc variables won't
behave like random cyclic, if new() is called for every randomization. In the following example
variable Var is not behaving like random cyclic.
EXAMPLE:
class rand_c;
randc bit [1:0]Var;
endclass
program rand_cp_12;
rand_c obj=new();
initial
for(int i=0;i<20;i++)
begin
obj=new();
void'(obj.randomize());
$write("%0d_",obj.Var);
if(i%4==3)
$display("");
end
endprogram
RESULTS:
# 1_3_1_2_
# 3_2_2_1_
# 2_0_0_0_
# 3_3_1_0_
# 3_0_1_0_
RANDOMIZATION METHODS
Randomization Built-In Methods
SystemVerilog has randomize(),pre_randomize() and post_randomize() built-in functions for
randomization. Calling randomize() causes new values to be selected for all of the random
variables in an object. To perform operations immediately before or after
randomization,pre_randomize() and post_randomize() are used.
Randomize()
Every class has a virtual predefined function randomize(), which is provided for generating a
new value.Randomization function returns 1 if the solver finds a valid solution. We cannot
override this predefined function. It is strongly recommended to check the return value of
randomize function. Constraint solver never fails after one successful randomization, if solution
space is not changed. For every randomization call, check the return value, solver may fail due to
dynamically changing the constraints.In the following example, there is no solution for Var <
100 and Var > 200,so the randomization fails.
The best way to check status of randomization return value is by using assertion.
assert(obj.randomize());
EXAMPLE:
program Simple_pro_13;
class Simple;
rand integer Var;
constraint c1 { Var <100;}
constraint c2 { Var >200;}
endclass
initial
begin
Simple obj = new();
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# Randomization failed
If randomize() fails, the constraints are infeasible and the random variables retain their previous
values. In the following example, For the first randomization call there is a solution. When the
constraints are changed, the randomization failed. Simulation results show that after
randomization failed, random variables hold their previous values.
EXAMPLE:
program Simple_pro_14;
class Simple;
rand integer Var;
integer MIN = 20 ;
constraint c { Var < 100 ; Var > MIN ; }
endclass
Simple obj;
initial
begin
obj = new();
if(obj.randomize())
$display(" Randomization successful : Var = %0d ",obj.Var);
else
$display("Randomization failed: Var = %0d ",obj.Var);
obj.MIN = 200;
$display(" MIN is changed to fail the constraint");
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display(" Randomization failed : Var = %0d",obj.Var);
end
endprogram
RESULTS:
# Randomization sucsessfull : Var = 87
# MIN is changed to fail the constraint.
# Randomization failed : Var = 87
Pre_randomize And Post_randomize
Every class contains pre_randomize() and post_randomize() methods, which are automatically
called by randomize() before and after computing new random values. When randomize() is
called,it first invokes the pre_randomize() then randomize() finally if the randomization is
sucesusful only post_randomize is invoked.
These methods can be used as hooks for the user to perform operations such as setting initial
values and performing functions after assigning random variables.
EXAMPLE:
program pre_post_15;
class simple;
function void pre_randomize;
$display(" PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" POST_RANDOMIZATION ");
endfunction
endclass
simple obj = new();
initial
void'(obj.randomize());
endprogram
RESULTS:
# PRE_RANDOMIZATION
# POST_RANDOMIZATION
Overriding of pre_randomize and post_randomize functions is allowed by child class. If parent
class functions are not called when overriding pre_randomize() and post_randomize functions,
parent class function definations will be omitted.
EXAMPLE:
class Base;
function void pre_randomize;
$display(" BASE PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" BASE POST_RANDOMIZATION ");
endfunction
endclass
class Extend_1 extends Base;
function void pre_randomize;
$display(" EXTEND_1 PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" EXTEND_1 POST_RANDOMIZATION ");
endfunction
endclass
class Extend_2 extends Base;
function void pre_randomize;
super.pre_randomize();
$display(" EXTEND_2 PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
super.post_randomize();
$display(" EXTEND_2 POST_RANDOMIZATION ");
endfunction
endclass
program pre_post_16;
Base B = new();
Extend_1 E1 = new();
Extend_2 E2 = new();
initial
begin
void'(B.randomize());
void'(E1.randomize());
void'(E2.randomize());
end
endprogram
In the extended class EXTEND_1, when overiding the builtin functions, parent class functions
are not called. In the extended class EXTEND_2, super.methods are called which invokes the
parent class methods also.
RESULTS:
# BASE PRE_RANDOMIZATION
# BASE POST_RANDOMIZATION
# EXTEND_1 PRE_RANDOMIZATION
# EXTEND_1 POST_RANDOMIZATION
# BASE PRE_RANDOMIZATION
# EXTEND_2 PRE_RANDOMIZATION
# BASE POST_RANDOMIZATION
# EXTEND_2 POST_RANDOMIZATION
The pre_randomize() and post_randomize() methods are not virtual. However, because they are
automatically called by the randomize() method, which is virtual, they appear to behave as
virtual methods. This example demonstrates that these functions are not virtual but simulation
results show that, it executed extended class definition functions. Extended class object is created
and assigned to base class object. Calls to pre_randomize and post_randomize calls in object B
,executed the extended class definitions.
EXAMPLE:
class Base;
function void pre_randomize;
$display(" BASE PRE_RANDOMIZATION ");
endfunction
virtual function void post_randomize;
$display(" BASE POST_RANDOMIZATION ");
endfunction
endclass
class Extend extends Base;
function void pre_randomize;
$display(" EXTEND PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" EXTEND POST_RANDOMIZATION ");
endfunction
endclass
program pre_post_17;
Base B ;
Extend E = new();
initial
begin
B=E;
void'(B.randomize());
void'(E.randomize());
end
endprogram
RESULTS:
program pre_post;
Base B;
Extend E = new();
initial
begin
B = E;
if(B.randomize())
$display(" randomization done \n");
end
endprogram
RESULTS:
BASE PRE_RANDOMIZATION
EXTENDED PRE_RANDOMIZATION
EXTENDED POST_RANDOMIZATION
randomization done
Results show that, if extended class is having new definition, explicitly super.pre_ or post_ has to
be called.
super.pre_randomize() is called in extended class, but super.post_randomize() is not called in
above example. See the difference in results.
If a class A instance is in Class B,To randomize class A by calling the randomize function of
class B,Class A instance has to be declared as rand variable.
EXAMPLE:
class A;
rand integer Var;
endclass
class B;
rand A obj_1 = new() ;
A obj_2 = new();
endclass
program a_b_20;
B obj=new();
initial
begin
obj.obj_1.Var = 1;
obj.obj_2.Var = 1;
repeat(10)
begin
void'(obj.randomize());
$display(" Var1 = %d ,Var2 = %d ",obj.obj_1.Var,obj.obj_2.Var );
end
end
endprogram
RESULTS:
# Var1 = 733126180 ,Var2 = 1
# Var1 = -119008195 ,Var2 = 1
# Var1 = 342785185 ,Var2 = 1
# Var1 = 679818185 ,Var2 = 1
# Var1 = -717162992 ,Var2 = 1
# Var1 = 664520634 ,Var2 = 1
# Var1 = -1944980214 ,Var2 = 1
# Var1 = -1350759145 ,Var2 = 1
# Var1 = -1374963034 ,Var2 = 1
# Var1 = -462078992 ,Var2 = 1
Look at the results. Variable of obj_2 is not randomized. Only variable of obj_1 which is
declared as rand is ranomized.
Upon calling the randomize method of B object which contains rand A object, First B
prerandomize is called, then A prerandomize method is called, then B is randomized, if a
solution was found, new values are assigned to the random A objects.If solution was found, for
each random object that is a class instance it's post_randomize method is called. That means if
randomization is successful next B postrandomize, next A postrandomize functions are called.
Upon calling B randomize function this is sequence it follow.
B-Prerandomize --> A-prerandomize --> A.randomize --> B-postrandomize --> A-
postrandomize
EXAMPLE:
class A;
rand integer Var;
function void pre_randomize;
$display(" A PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" A POST_RANDOMIZATION ");
endfunction
endclass
class B;
rand A obj_a;
function new();
obj_a = new();
endfunction
function void pre_randomize;
$display(" B PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" B POST_RANDOMIZATION ");
endfunction
endclass
program pre_post_21;
B obj_b = new();
initial
void'(obj_b.randomize());
endprogram
RESULTS:
# B PRE_RANDOMIZATION
# A PRE_RANDOMIZATION
# B POST_RANDOMIZATION
# A POST_RANDOMIZATION
If randomization failed for obj_a, then post_randomize of obj_a and post_randomize of obj_b
won't be called, and randomization will fail for obj_b also.
EXAMPLE:
class A;
rand bit [2:0] Var;
constraint randge_c { Var > 2 ; Var < 2;}
function void pre_randomize;
$display(" A PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" A POST_RANDOMIZATION ");
endfunction
endclass
class B;
rand A obj_a;
function void pre_randomize;
$display(" B PRE_RANDOMIZATION ");
endfunction
function void post_randomize;
$display(" B POST_RANDOMIZATION ");
endfunction
function new();
obj_a = new();
endfunction
endclass
program pre_post_22;
B obj_b = new();
initial
void'(obj_b.randomize());
endprogram
RESULTS:
# B PRE_RANDOMIZATION
# A PRE_RANDOMIZATION
Disabling Random Variable
The random nature of variables declared as rand or randc can be turned on or off dynamically.
To change the status of variable which is declared as rand or randc to state variable, built in
rand_mode() method is used. State variables are not randomized by randomize() mehod. By
default all rand and randc variables are active. When called as a task, the arguments to the
rand_mode method determines the operation to be performed. If the arguments is 0, then all the
variables declared as rand and randc will become non random i.e all random variables treated as
state variables. IF argument is 1, then all variables declares as rand and randc will be
randomized.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
endclass
program rand_mo_p_23;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.rand_mode(0); // Var1 and Var2 will be treated as State variables.
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.rand_mode(1); // // Var1 and Var2 will be treated as random variables.
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
RESULTS:
# Var1 : 733126180 Var2 : -119008195
# Var1 : 733126180 Var2 : -119008195
# Var1 : 342785185 Var2 : 679818185
If arguments are Variable name, then only that variable will be non random.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
endclass
program rand_mo_p_24;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var1.rand_mode(0); // Var1 will become State variable
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var2.rand_mode(0); // Var2 will also become State variable
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var1.rand_mode(1); // // Var1 will become random variable
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
RESULTS:
# Var1 : 733126180 Var2 : -119008195
# Var1 : 733126180 Var2 : 342785185
# Var1 : 733126180 Var2 : 342785185
# Var1 : 679818185 Var2 : 342785185
When rand_mode method is called as function, it returns the active status of the specified
random variable.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
endclass
program rand_mo_p_24;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var1.rand_mode(0);
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
if(obj.Var1.rand_mode())
$display(" Var1 is random");
else
$display(" Var1 is nonrandom");
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
RESULTS:
# Var1 : 733126180 Var2 : -119008195
# Var1 : 733126180 Var2 : 342785185
# Var1 is nonrandom
# Var1 : 733126180 Var2 : 679818185
If you are changing the status of a variable, which is not existing it is compilation error.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
endclass
program rand_mo_p_24;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var3.rand_mode(0);
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
if(obj.Var3.rand_mode())
$display(" Var3 is nonrandom");
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
A compiler error shall be issued if the specified variable does not exist within the class hierarchy
or eventhough it exists but not declared as rand or randc. The following example illustrates the
second case.
EXAMPLE:
class rand_mo;
rand integer Var1;
integer Var2;
endclass
program rand_mo_p_24;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var2.rand_mode(0);
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
if(obj.Var2.rand_mode())
$display(" Var1 is nonrandom");
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
In the above example, Var2 is state variable. If the random variable is an object handle, only
the mode of the object is changed, not the mode of random variables within that object.
EXAMPLE:
class rand_var;
rand integer Var2;
endclass
class rand_mo;
rand integer Var1;
rand rand_var rv;
function new();
rv = new();
endfunction
endclass
program rand_mo_p_23;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.rv.Var2);
obj.rand_mode(0);
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.rv.Var2);
void'(obj.rv.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.rv.Var2);
end
endprogram
RESULTS:
Var1 : 345345423 Var2 : 234563556
Var1 : 345345423 Var2 : 234563556
Var1 : 345345423 Var2 : -2456456
Random Static Variable
Randomization does not depend on life time of variable. Even if a variable is
static,randomization is specfic to object.So rand_mode() on static variable,only switches off the
randomization on the variable of that object.This is true for dist and randc.
In the following example, Var1 is static and Var2 is automatic.Var1 and Var2 in obj_2 are made
nonrandom using rand_mode(0).Var1 and Var2 in obj_1 are getting randomized.The only
difference between Var1 and Var2 is that new random value for Var1 is appreas on both objects.
EXAMPLE:
class A;
rand static integer Var1;
rand integer Var2;
endclass
program A_p_27;
A obj_1 = new;
A obj_2 = new;
initial
begin
obj_2.Var1.rand_mode(0);
obj_2.Var2.rand_mode(0);
repeat(2)
begin
void'(obj_1.randomize());
void'(obj_2.randomize());
$display("obj_1.Var1 : %d ,obj_1.Var2 : %d : obj_2.Var1 : %d ,obj_2.Var2 : %d
:",obj_1.Var1,obj_1.Var2,obj_2.Var1,obj_2.Var2);
end
end
endprogram
RESULTS:
obj_1.Var1 : 934734534,obj_1.Var2 : 234232342: obj_2.Var1 : 934734534 ,obj_2.Var2 : 0 :
obj_1.Var1 : 908123314,obj_1.Var2 : 121891223: obj_2.Var1 : 908123314 ,obj_2.Var2 : 0 :
Random variables declared as static are shared by all instances of the class in which they are
declared. Each time the randomize() method is called, the variable is changed in every class
instance.
Randomizing Nonrand Varible
All the variables(randc, rand and nonrandom variables) randomization nature can be changed
dynamically. Using rand_mode() rand and randc varibles changes its nature. The random nature
of variables which are not declared as rand or randc can also be randomized dynamically. When
the randomize method is called with no arguments, it randomizes the variables which are
declared as rand or randc,so that all of the constraints are satisfied. When randomize is called
with arguments, those arguments designate the complete set of random variables within that
object, all other variables in the object are considered state variables.
EXAMPLE:
class CA;
rand byte x, y;
byte v, w;
constraint c1 { x < v && y > w ;}
endclass
program CA_p_28;
CA a = new;
initial
begin
a.x = 10;a.y = 10;a.v = 10;a.w = 10;
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize()); // random variables: x, y state variables: v, w
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize(x)); // random variables: x state variables: y, v, w
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize(v,w)); // random variables: v, w state variables: x, y
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize(w,x)); // random variables: w, x state variables: y, v
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
end
endprogram
RESULTS:
# x : 10 y : 10 : v : 10 : w : 10
# x : -71 y : 96 : v : 10 : w : 10
# x : -37 y : 96 : v : 10 : w : 10
# x : -37 y : 96 : v : -22 : w : 80
# x : -90 y : 96 : v : -22 : w : -41
In above example x and y are rand variables, v and w are state variables. When a.randomize() is
called, all rand varibles are randomized and state variables are hold the same value. When
a.randomize(w) is called, only w is considered as rand variable and all others as state varibles.
Here w is in constraint block so it has to satisfy the constraints. v,y and x also are state variables
now, they also need to satisfy the constraint else it fails.
Replacing the class variables, with its hierarchical result also should result same.
EXAMPLE:
program CA_p_29;
CA a = new;
initial
begin
a.x = 10;a.y = 10;a.v = 10;a.w = 10;
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize()); // random variables: x, y state variables: v, w
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize( a.x )); // random variables: x state variables: y, v, w
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize( a.v, a.w )); // random variables: v, w state variables: x, y
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
void'(a.randomize( a.w, a.x )); // random variables: w, x state variables: y, v
$display(" x : %3d y : %3d : v : %3d : w : %3d ",a.x,a.y,a.v,a.w);
end
endprogram
RESULTS:
# x : 10 y : 10 : v : 10 : w : 10
# x : -71 y : 96 : v : 10 : w : 10
# x : -37 y : 96 : v : 10 : w : 10
# x : -37 y : 96 : v : -22 : w : 80
# x : -90 y : 96 : v : -22 : w : -41
If you are not interested to satisfy the constraints in constraint block, instead of switching off the
constraint block, just randomize the variables using scope randomize() function. Scope
randomize provide the ability to randomize class variables also along with non class variables.
EXAMPLE:
program CA_p_30;
integer x,y,v,w;
initial
begin
x = 10;y = 10;v = 10;w = 10;
$display(" x : %3d y : %3d : v : %3d : w : %3d ",x,y,v,w);
randomize( x ); // random variables: x state variables: y, v, w
$display(" x : %3d y : %3d : v : %3d : w : %3d ",x,y,v,w);
randomize(v,w ); // random variables: v, w state variables: x, y
$display(" x : %3d y : %3d : v : %3d : w : %3d ",x,y,v,w);
randomize(w,x ); // random variables: w, x state variables: y, v
$display(" x : %3d y : %3d : v : %3d : w : %3d ",x,y,v,w);
end
endprogram
RESULTS:
# x : 10 y : 10 : v : 10 : w : 10
# x : -826701341 y : 10 : v : 10 : w : 10
# x : -826701341 y : 10 : v : 541037099 : w : -457978538
# x : 978699393 y : 10 : v : 541037099 : w : -683182079
CHECKER
Built-in method randomize() not only used for randomization, it can be used as checker. When
randomize() method is called by passing null, randomize() method behaves as checker instead of
random generator. It evaluates all the constraints and returns the status. This is true for both
scope randomization function and class randomization function. When a randomize() method is
called, first RNG assigns values to random varibles and then solver checks the constraints. When
randomize(null) is called, it wont call the RNG to assign values to random variables, it just
solves constraints.
EXAMPLE:
class Eth_rx;
rand integer Pkt_len;
rand integer Var;
constraint var_c { Var < 1518 ;Var > 64 ;}
endclass
program Eth_25;
Eth_rx rx = new();
initial
begin
rx.Pkt_len = 32;
rx.Var = 871;
if(rx.randomize(null))
$display(" VALID PKT IS RECIVED ");
else
$display(" INVALID PKT IS RECIVED ");
end
endprogram
RESULTS:
# VALID PKT IS RECIVED
Constraints can be written without having random varibles in expressions. If there is any
constraint on state variables and they are dynamically changed, and if you want to make sure that
these dynamic changes should satisfy the constraint, use randomize check to make sure that
relation is satisfied.
In the following example, MIN and MAX are dynamically controllable state variables.
Constraint checker_c fails when MIn = 50 and MAX = 10.
EXAMPLE:
class Base;
rand integer Var;
integer MIN,MAX;
constraint randge_r { Var < MAX ; Var > MIN ;}
constraint checker_c{ MIN < MAX ;} // This checks that these dynamic variables are valid
task set (integer MIN,integer MAX);
this.MIN = MIN;
this.MAX = MAX;
$display( " SET : MIN = %0d , MAX = %0d ",MIN,MAX);
endtask
endclass
program inhe_26;
Base obj;
initial
begin
obj = new();
obj.set(0,100) ;
for(int i=0 ; i < 5 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
obj.set(50,10) ;
for(int i=0 ; i < 5 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# SET : MIN = 0 , MAX = 100
# Randomization sucsessfull : Var = 68
# Randomization sucsessfull : Var = 11
# Randomization sucsessfull : Var = 8
# Randomization sucsessfull : Var = 36
# Randomization sucsessfull : Var = 64
# SET : MIN = 50 , MAX = 10
# Randomization failed
# Randomization failed
# Randomization failed
# Randomization failed
# Randomization failed
CONSTRAINT BLOCK
Constraint block contains declarative statements which restrict the range of variable or defines
the relation between variables. Constraint programming is a powerful method that lets users
build generic, resuble objects that can be extended or more constrained later. constraint solver
can only support 2 stete values. If a 4 state variable is used, solver treates them as 2 state
variable.. Constraint solver fails only if there is no solution which satisfies all the constraints.
Constraint block can also have nonrandom variables, but at least one random variable is needed
for randomization. Constraints are tied to objects. This allows inheritance, hierarchical
constraints, controlling the constraints of specific object.
Inheritance
One of the main advantage of class randomization is Inheritance. Constraints in derived class
with the same name in base class overrides the base class constraints just like task and functions.
EXAMPLE:
class Base;
rand integer Var;
constraint range { Var < 100 ; Var > 0 ;}
endclass
class Extended extends Base;
constraint range { Var < 100 ; Var > 50 ;} // Overrighting the Base class constraints.
endclass
program inhe_31;
Extended obj;
initial
begin
obj = new();
for(int i=0 ; i < 100 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# Randomization sucsessfull : Var = 91
# Randomization sucsessfull : Var = 93
# Randomization sucsessfull : Var = 77
# Randomization sucsessfull : Var = 68
# Randomization sucsessfull : Var = 67
# Randomization sucsessfull : Var = 52
# Randomization sucsessfull : Var = 71
# Randomization sucsessfull : Var = 98
# Randomization sucsessfull : Var = 69
Adding new constraints in the derived class, can change the solution space. Solver has to solve
both constraints defined in base class and derived class. In the example given below, Constraint
range_1 defines the range that Var is between 0 to 100.Constraint range_2 limits the Var to be
greater than 50 and solver has to solve both the constraints and the solution space is between 50
to 100.
EXAMPLE:
class Base;
rand integer Var;
constraint range_1 { Var < 100 ; Var > 0 ;}
endclass
class Extended extends Base;
constraint range_2 { Var > 50 ;} // Adding new constraints in the Extended class
endclass
program inhe_32;
Extended obj;
initial
begin
obj = new();
for(int i=0 ; i < 20 ; i++)
if(obj.randomize())
$write(": Var = %0d :",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
Var = 91 :: Var = 93 :: Var = 77 :: Var = 68 :: Var = 67 :: Var = 52 :: Var = 71 :: Var = 98 :: Var
= 69 :: Var = 70 :: Var = 96 :: Var = 88 :: Var = 84 :: Var = 99 :: Var = 68 :: Var = 83 :: Var = 52
:: Var = 72 :: Var = 93 :: Var = 80 :
Overrighting Constraints
The randomize() task is virtual. Accordingly it treats the class constraints in a virtual manner.
When a named constraint is redefined in an extended class, the previous definition is overridden
and when casting extended class to base class does not change the constraint set.
EXAMPLE:
class Base;
rand integer Var;
constraint range { Var < 100 ; Var > 0 ;}
endclass
class Extended extends Base;
constraint range { Var == 100 ;} // Overrighting the Base class constraints.
endclass
program inhe_33;
Extended obj_e;
Base obj_b;
initial
begin
obj_e = new();
obj_b = obj_e;
for(int i=0 ; i < 7 ; i++)
if(obj_b.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj_b.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# Randomization sucsessfull : Var = 100
# Randomization sucsessfull : Var = 100
# Randomization sucsessfull : Var = 100
# Randomization sucsessfull : Var = 100
# Randomization sucsessfull : Var = 100
# Randomization sucsessfull : Var = 100
# Randomization sucsessfull : Var = 100
When an extended object is casted to base object, all the constraints in extended object are
solved along with the constraints in base object.
EXAMPLE:
class Base;
rand integer Var;
constraint range_1 { Var < 100 ; Var > 0 ;}
endclass
class Extended extends Base;
constraint range_2 { Var > 50 ;}
endclass
program inhe_34;
Extended obj_e;
Base obj_b;
initial
begin
obj_e = new();
obj_b = obj_e;
for(int i=0 ; i < 10 ; i++)
if(obj_b.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj_b.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# Randomization sucsessfull : Var = 91
# Randomization sucsessfull : Var = 93
# Randomization sucsessfull : Var = 77
# Randomization sucsessfull : Var = 68
# Randomization sucsessfull : Var = 67
# Randomization sucsessfull : Var = 52
# Randomization sucsessfull : Var = 71
# Randomization sucsessfull : Var = 98
# Randomization sucsessfull : Var = 69
# Randomization sucsessfull : Var = 70
INLINE CONSTRAINT
Inline constraints allows to add extra constraints to already existing constraints which are
declared inside class. If you have constraints already defined for variavle var, solver solves those
constraints along with the in-line constraints.
EXAMPLE:
class inline;
rand integer Var;
constraint default_c { Var > 0 ; Var < 100;}
endclass
program inline_p_35;
inline obj;
initial
begin
obj = new();
repeat(5)
if(obj.randomize() with { Var == 50;})
$display(" Randodmize sucessful Var %d ",obj.Var);
else
$display(" Randomization failes");
end
endprogram
RESULTS:
# Randodmize sucessful Var 50
# Randodmize sucessful Var 50
# Randodmize sucessful Var 50
# Randodmize sucessful Var 50
# Randodmize sucessful Var 50
In the above example, by default only default_c constraints are considered. Using inline
constraint Var == 50 resulted value on variable Var based on both the default_c and inline
constraints. The scope for variable names in a constraint block, from inner to outer, is
randomize()...with object class, automatic and local variables, task and function arguments, class
variables, and variables in the enclosing scope. The randomize()...with class is brought into
scope at the innermost nesting level. In the f.randomize() with constraint block, x is a member of
class Foo and hides the x in program Bar. It also hides the x argument in the doit() task. y is a
member of Bar. z is a local argument.
In the example below, the randomize()...with class is Foo.
EXAMPLE:
class Foo;
rand integer x;
endclass
program Bar_36;
Foo obj = new();
integer x;
integer y;
task doit(Foo f, integer x, integer z);
int result;
result = f.randomize() with {x < y + z;};
$display(":: obj.x : %d :: x : %d :: y : %d :: z : %d ::",obj.x,x,y,z);
endtask
initial
begin
x = 'd10;
repeat(5)
begin
y = $urandom % 10;
doit(obj,x ,'d12);
end
end
endprogram
RESULTS:
:: obj.x : -1538701546 :: x : 10 :: y : 5 :: z : 12 ::
:: obj.x : -1048494686 :: x : 10 :: y : 9 :: z : 12 ::
:: obj.x : -1122673684 :: x : 10 :: y : 8 :: z : 12 ::
:: obj.x : -2050360121 :: x : 10 :: y : 7 :: z : 12 ::
:: obj.x : -886228933 :: x : 10 :: y : 3 :: z : 12 ::
By seeing this output we can tell the variable which used in inline constraint is class member x.
constraint { x < y + z } means { obj.x < Bar_36.y + do_it.z }
GLOBAL CONSTRAINT
SystemVerilog allows to have constraints between variables of different objects. These are called
global constraints. Using the hierarchy notation, constraints can be applied on variables of
different objects. When object is randomized, so are the contained objects and all other
constraints are considered simultaneously.
EXAMPLE:
class child;
rand int Var1;
endclass
class parent;
rand child child_obj;
rand int Var2;
constraint global_c { Var2 < child_obj.Var1 ;}
function new();
child_obj = new();
endfunction
endclass
program random_37;
initial
for(int i=0;i<5;i++)
begin
parent parent_obj;
parent_obj = new ();
void'(parent_obj.randomize ());
$display(" Var1 = %0d ,Var2 = %0d ",parent_obj.child_obj.Var1,parent_obj.Var2 );
end
endprogram
RESULTS:
# Var1 = 903271284 ,Var2 = -1102515531
# Var1 = 2112727279 ,Var2 = -838916208
# Var1 = 1614679637 ,Var2 = 1572451945
# Var1 = 1284140376 ,Var2 = -373511538
# Var1 = 463675676 ,Var2 = -516850003
CONSTRAINT MODE
Disabling Constraint Block
SystemVerilog supports to change the status of constraint block dynamically. To change the
status of a Constraint block, built in constraint_mode() method is used. By default all the
constraint blocks are active. When it is called as task, the arguments to the task determines the
operation to be performed. If the arguments are 0 or 1, then all the constraints blocks are
effected.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
constraint Var_1 { Var1 == 20;}
constraint Var_2 { Var2 == 10;}
endclass
program rand_mo_p_38;
rand_mo obj = new();
initial
begin
void'(obj.randomize()); //By default all constraints are active.
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.constraint_mode(0); //Both constraints Var_1 and Var_2 are turned off.
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.constraint_mode(1); //Both constraints Var_1 and Var_2 are turned on.
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
RESULTS:
# Var1 : 20 Var2 : 10
# Var1 : 733126180 Var2 : -119008195
# Var1 : 20 Var2 : 10
If the arguments are Variable name, then only the status of that constraint block is changed.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
constraint Var_1 { Var1 == 20;}
constraint Var_2 { Var2 == 10;}
endclass
program rand_mo_p_38;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var_1.constraint_mode(0);
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var_1.constraint_mode(1);
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
RESULTS:
Var1 : 20 Var2 : 10
Var1 : 3w456456 Var2 : 10
Var1 : 20 Var2 : 10
When it is called as function, it returns the active status of the specified constraint block.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
constraint Var_1 { Var1 == 20;}
constraint Var_2 { Var2 == 10;}
endclass
program rand_mo_p_38;
rand_mo obj = new();
initial
begin
void'(obj.randomize()); //By default all constraints are active.
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var_1.constraint_mode(0); //Both constraint Var_1 is are turned off.
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
if (obj.Var_1.constraint_mode())
$display("Var_1 constraint si active");
else
$display("Var_1 constraint si inactive");
if (obj.Var_2.constraint_mode())
$display("Var_2 constraint si active");
else
$display("Var_2 constraint si inactive");
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
RESULTS:
Var1 : 20 Var2 : 10
Var1 : -2048772810 Var2 : 10
Var_1 constraint si inactive
Var_2 constraint si active
Var1 : -673275588 Var2 : 10
If you are changing the status of a constraint block, which is not existing, then there should be a
compilation error else contact your Application engineer to file the bug.
EXAMPLE:
class rand_mo;
rand integer Var1;
rand integer Var2;
constraint Var_1 { Var1 == 20;}
constraint Var_2 { Var2 == 10;}
endclass
program rand_mo_p_38;
rand_mo obj = new();
initial
begin
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
obj.Var_3.constraint_mode(0);
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
if(obj.Var_3.constraint_mode())
$display(" Vat_1 constraint block is off");
void'(obj.randomize());
$display(" Var1 : %d Var2 : %d ",obj.Var1,obj.Var2);
end
endprogram
EXTERNAL CONSTRAINTS
Constraint blocks can be defined externally in the same file or other files. Defining the
constraints in external file, gives some what aspect oriented style of programming.
For example, test_1 requires Var between 0 to 100 and tets_2 requires Var between 50 to
100.Declare the constraint block as empty and define them in other files.
EXAMPLE:
class Base;
rand integer Var;
constraint range;
endclass
program inhe_39;
Base obj;
initial
begin
obj = new();
for(int i=0 ; i < 100 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
In test_1 file,include
constraint Base::range { Var < 100; Var > 0;}
In test_2 file,include
constraint Base::range { Var < 100; Var > 50;}
Very popular verilog style of including testcases is by using `include which can also be used
hear.
Write the constraints in a file and include it.
//
EXAMPLE:
class Base;
rand integer Var;
`include "constraint.sv"
endclass
program inhe_40;
Base obj;
initial
begin
obj = new();
for(int i=0 ; i < 100 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
Constraint Hiding
In SV Std 1800-2005 LRM , its not mentioned any where that constraints can be declared as
local or protected. If they support to declare constraints as local, it would be helpful not to
switchoff the constraint block accedentally is it is not supposed to be done. The constraint BNF
explicitly excludes the local and protected modifiers. The main reason for their exclusion is
because constraints behave like virtual methods that are called by the built-in randomize method.
If a constraint were declared local/protected it would still be visible to randomize and participate
in the constraint equations. The only limitation would be to call the constraint_mode on
local/protected constraints from certain methods, and this does not seem very useful and
probably create more confusion with regards to overridden methods.
RANDOMIZATION CONTROLABILITY
Controlability
Additional to the controllability feauters supported by SystemVerilog, following are more points
with which controlabiity can be achieved.
In the following example, MACROS MIN_D and MAX_D are defined. Set the MIN and MAX
values in the pre_randomize as shown. As MIN_D and MAX_D are macros, they can be
assigned from command line. Biggest disadvantage for the method shown below is dynamic
controllability.
EXAMPLE:
`define MAX_D 100
`define MIN_D 50
class Base;
rand integer Var;
integer MIN,MAX;
constraint randge { Var < MAX ; Var > MIN ;}
function void pre_randomize ();
this.MIN = `MIN_D;
this.MAX = `MAX_D;
$display( " PRE_RANDOMIZE : MIN = %0d , MAX = %0d ",MIN,MAX);
endfunction
endclass
program inhe_42;
Base obj;
initial
begin
obj = new();
for(int i=0 ; i < 100 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 91
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 93
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 77
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 68
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 67
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 52
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 71
# PRE_RANDOMIZE : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 98
...etc.
As in this example,a single object is created and randomized 100 times. Due to
this,pre_reandomize is called 100 times, which may not be preferred.
By assigning the values while declaration itself this can be avoided. Simpler way to achieve the
above logic.
EXAMPLE:
`define MAX_D 100
`define MIN_D 50
class Base;
rand integer Var;
integer MIN = `MIN_D;
integer MAX = `MAX_D;
constraint range { Var < MAX ; Var > MIN ;}
endclass
program inhe_43;
Base obj;
initial
begin
obj = new();
for(int i=0 ; i < 100 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# Randomization sucsessfull : Var = 91
# Randomization sucsessfull : Var = 93
# Randomization sucsessfull : Var = 77
# Randomization sucsessfull : Var = 68
# Randomization sucsessfull : Var = 67
# Randomization sucsessfull : Var = 52
# Randomization sucsessfull : Var = 71
# Randomization sucsessfull : Var = 98
# Randomization sucsessfull : Var = 69
# Randomization sucsessfull : Var = 70
...etc.
With the above approach also, dynamic controlability is lost. For dynamic controllability, define
a task, pass this values as arguments when ever the changed is needed.
EXAMPLE:
class Base;
rand integer Var;
integer MIN = 10,MAX = 20; // Define default values,If function set is not called,with this it
will work
constraint randge { Var < MAX ; Var > MIN ;}
task set (integer MIN,integer MAX);
this.MIN = MIN;
this.MAX = MAX;
$display( " SET : MIN = %0d , MAX = %0d ",MIN,MAX);
endtask
endclass
program inhe_44;
Base obj;
initial
begin
obj = new();
obj.set(0,100) ;
for(int i=0 ; i < 5 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
obj.set(50,100) ;
for(int i=0 ; i < 5 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# SET : MIN = 0 , MAX = 100
# Randomization sucsessfull : Var = 24
# Randomization sucsessfull : Var = 68
# Randomization sucsessfull : Var = 43
# Randomization sucsessfull : Var = 11
# Randomization sucsessfull : Var = 4
# SET : MIN = 50 , MAX = 100
# Randomization sucsessfull : Var = 52
# Randomization sucsessfull : Var = 71
# Randomization sucsessfull : Var = 98
# Randomization sucsessfull : Var = 69
# Randomization sucsessfull : Var = 70
More simpler way to dynamically modifying the constraints is by modifying the data members of
class via object reference.
EXAMPLE:
class Base;
rand integer Var;
integer MIN = 20,MAX =30;
constraint randge { Var < MAX ; Var > MIN ;}
endclass
program inhe_45;
Base obj;
initial
begin
obj = new();
obj.MIN = 0;
obj.MAX = 100;
for(int i=0 ; i < 5 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
$display("MIN and MAX changed");
obj.MIN = 50;
obj.MAX = 100;
for(int i=0 ; i < 5 ; i++)
if(obj.randomize())
$display(" Randomization sucsessfull : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
# Randomization sucsessfull : Var = 24
# Randomization sucsessfull : Var = 68
# Randomization sucsessfull : Var = 43
# Randomization sucsessfull : Var = 11
# Randomization sucsessfull : Var = 4
# MIN and MAX changed
# Randomization sucsessfull : Var = 52
# Randomization sucsessfull : Var = 71
# Randomization sucsessfull : Var = 98
# Randomization sucsessfull : Var = 69
# Randomization sucsessfull : Var = 70
STATIC CONSTRAINT
If a constraint block is declared as static, then constraint mode on that block will effect on all the
instancess of that class. In the following example, two objects obj_1 and obj_2 are declared.
Constraints Var1_c is static and Var2_c is not static. When constraint_mode is used to switch off
the constraints in in obj_2, constraint var1_c in obj_1 is also switched off.
EXAMPLE:
class A;
rand integer Var1, Var2;
static constraint Var1_c { Var1 == 10 ;}
constraint Var2_c { Var2 == 10 ;}
endclass
program A_p_46;
A obj_1 = new;
A obj_2 = new;
initial
begin
obj_2.Var1_c.constraint_mode(0);
obj_2.Var2_c.constraint_mode(0);
repeat(10)
begin
void'(obj_1.randomize());
$display("obj_1.Var1 : %d ,obj_1.Var2 : %d ",obj_1.Var1,obj_1.Var2);
end
end
endprogram
RESULTS:
# obj_1.Var1 : 733126180 ,obj_1.Var2 : 10
# obj_1.Var1 : -119008195 ,obj_1.Var2 : 10
# obj_1.Var1 : 342785185 ,obj_1.Var2 : 10
# obj_1.Var1 : 679818185 ,obj_1.Var2 : 10
# obj_1.Var1 : -717162992 ,obj_1.Var2 : 10
# obj_1.Var1 : 664520634 ,obj_1.Var2 : 10
# obj_1.Var1 : -1944980214 ,obj_1.Var2 : 10
# obj_1.Var1 : -1350759145 ,obj_1.Var2 : 10
# obj_1.Var1 : -1374963034 ,obj_1.Var2 : 10
# obj_1.Var1 : -462078992 ,obj_1.Var2 : 10
CONSTRAINT EXPRESSION
A constraint_expression is any SystemVerilog expression or one of the constraint specific
operators( -> (Implication) and dist). Functions are allowed to certain limitation. Operators
which has side effects are not allowed like ++,--.
Set Membership
A set membership is a list of expressions or a range. This operator searches for the existences of
the value in the specified expression or range and returns 1 if it is existing.
EXAMPLE:
class set_mem;
rand integer Var;
constraint range { Var inside {0,1,[50:60],[90:100]}; }
function void post_randomize();
$write("%0d__",Var);
endfunction
endclass
program set_mem_p_47;
set_mem obj=new();
initial
repeat(10)
void'(obj.randomize());
endprogram
RESULTS:
50__57__60__93__100__94__90__1__54__100__
If you want to define a range which is outside the set, use negation.
EXAMPLE:
class set_mem;
rand bit [0:2] Var;
constraint range { !( Var inside {0,1,5,6});}
function void post_randomize();
$write("%0d__",Var);
endfunction
endclass
program set_mem_p_48;
set_mem obj=new();
initial
repeat(10)
void'(obj.randomize());
endprogram
RESULTS:
7__4__4__4__7__2__2__3__2__7__
Engineers often mistaken that set membership operator is used only in constraint block. It can
also be used in other scopes also.
class set_mem;
rand bit [0:2] Var;
endclass
program set_mem_p_48;
set_mem obj=new();
integer repet = 0;
initial
begin
obj.Var = 1;
repeat(10)
begin
void'(obj.randomize());
while ( obj.Var inside {[1:5]})
begin
$display("Var = %0d",obj.Var);
break;
end
end
end
endprogram
RESULTS:
# Var = 4
# Var = 5
# Var = 1
# Var = 1
# Var = 2
# Var = 2
NOTE: X and Z are allowed in set membership operator, but not in constraint block, inside {....}
is a statement which returns 0,1 or X .
Expressions are allowed in value set of inside operator.
rand integer y,x;
constraint c1 {x inside {3, 5,[y:2*y], z};}
If an expression in the list is an array then just use the array name in the constraint block.
Elements are traversed by descending into the array until reaching a singular value.
int array [$] = '{3,4,5};
if ( ex inside {1, 2, array} )
Weighted Distribution
There are two types of distribution operators.
The := operator assigns the specified weight to the item or, if the item is a range, to every value
in the range.
The :/ operator assigns the specified weight to the item or, if the item is a range, to the range as a
whole. If there are n values in the range, the weight of each value is range_weight / n.
Var dist { 10 := 1; 20 := 2 ; 30 := 2 }
The probability of Var is equal to 10,20 and 30 is in the ratio of 1,2,2 respectively.
Var dist { 10 := 1; 20 := 2 ; [30:32] := 2 }
The probability of Var is equal to 10,20,30,31 and 32 is in the ratio of 1,2,2,2,2 respectively.
If you use the := operator each element weitht of the range has the assigned weight.
If you want to weight for the whole group,use :/ and the weight is equally distributed for each
element in that group.
Var dist { 10 := 1; 20 := 2 ; [30:32] :/ 2 }
The probability of Var is equal to 10,20,30,31,32 is in the ration of 1,2,2/3,2/3,2/3 respectively.
To demonstrate the distribution property,hear is an example.
EXAMPLE:
class Dist;
rand integer Var;
constraint range { Var dist { [0:1] := 50 , [2:7] := 50 }; }
endclass
program Dist_p_49;
Dist obj;
integer count_0, count_1, count_2, count_3, count_4, count_5, count_6, count_7;
integer count_0_1 ,count_2_7 ;
initial
begin
obj=new();
count_0 = 0;count_1 = 0;count_2 = 0;count_3 = 0;
count_4 = 0;count_5 = 0;count_6 = 0;count_7 = 0;
count_0_1 = 0;count_2_7 = 0;
for(int i=0; i< 10000; i++)
if( obj.randomize())
begin
if( obj.Var == 0) count_0 ++;
else if( obj.Var == 1) count_1 ++;
else if( obj.Var == 2) count_2 ++;
else if( obj.Var == 3) count_3 ++;
else if( obj.Var == 4) count_4 ++;
else if( obj.Var == 5) count_5 ++;
else if( obj.Var == 6) count_6 ++;
else if( obj.Var == 7) count_7 ++;
if( obj.Var inside {0,1} ) count_0_1 ++;
else if( obj.Var inside {[2:7]} ) count_2_7 ++;
end
$display(" count_0 = %0d , count_1 = %0d, count_2 = %0d, count_3 = %0d, count_4 = %0d,
count_5 = %0d, count_6 = %0d, count_7= %0d
",count_0, count_1, count_2, count_3, count_4, count_5, count_6, count_7);
$display(" count_0_1 = %0d ;count_2_7 = %0d ",count_0_1,count_2_7);
$finish;
end
endprogram
RESULTS:
# count_0 = 1290 , count_1 = 1244, count_2 = 1286, count_3 = 1265, count_4 = 1230, count_5
= 1243, count_6 = 1189, count_7= 1253
# count_0_1 = 2534 ;count_2_7 = 7466
Now change the constraint to
constraint range { Var dist { [0:1] :/ 50 , [2:7] :/ 50 }; }
EXAMPLE:
class Dist;
rand integer Var;
constraint range { Var dist { [0:1] :/ 50 , [2:7] :/ 50 }; }
endclass
program Dist_p_50;
Dist obj;
integer count_0, count_1, count_2, count_3, count_4, count_5, count_6, count_7;
integer count_0_1 ,count_2_7 ;
initial
begin
obj=new();
count_0 = 0;count_1 = 0;count_2 = 0;count_3 = 0;
count_4 = 0;count_5 = 0;count_6 = 0;count_7 = 0;
count_0_1 = 0;count_2_7 = 0;
$display(" count_0 = %0d , count_1 = %0d, count_2 = %0d, count_3 = %0d, count_4 = %0d,
count_5 = %0d, count_6 = %0d, count_7= %0d
",count_0, count_1, count_2, count_3, count_4, count_5, count_6, count_7);
$display(" count_0_1 = %0d ;count_2_7 = %0d ",count_0_1,count_2_7);
$finish;
end
endprogram
RESULTS:
# count_0 = 2496, count_1 = 2508, count_2 = 846, count_3 = 824, count_4 = 833, count_5 =
862, count_6 = 820, count_7= 811
# count_0_1 = 5004 ;count_2_7 = 4996
Both the results show, how may times each value occured.
NOTE: If no weight is specified for items,the default weight is 1. Weight 0 is also allowed.
NOTE: Variable declared as randc are not allowed int dist.
If there are constraints on some expressions that cause the distribution weights on these
expressions to be not satisfiable, implementations are only required to satisfy the non dist
constraints. Use dist only on a one variable in a set of constraint expression variable.
In the following example, Even though probability of Var2 is equal to 0 to 1 is in ratio of 50, 50
to satisfy other constraints, the dist is ignored.
EXAMPLE:
class Dist;
rand integer Var1,Var2;
constraint dist_c { Var2 dist { [0:1] := 50 , [2:7] := 50 }; }
constraint relation_c { Var1 < Var2; }
constraint range_c { Var2 inside {2,3,4};}
endclass
program Dist_p_51;
Dist obj;
integer count_0, count_1, count_2, count_3, count_4, count_5, count_6, count_7;
integer count_0_1 ,count_2_7 ;
initial
begin
obj=new();
count_0 = 0;count_1 = 0;count_2 = 0;count_3 = 0;
count_4 = 0;count_5 = 0;count_6 = 0;count_7 = 0;
count_0_1 = 0;count_2_7 = 0;
$display(" count_0 = %0d , count_1 = %0d, count_2 = %0d, count_3 = %0d, count_4 = %0d,
count_5 = %0d, count_6 = %0d, count_7= %0d
",count_0, count_1, count_2, count_3, count_4, count_5, count_6, count_7);
$display(" count_0_1 = %0d ;count_2_7 = %0d ",count_0_1,count_2_7);
$finish;
end
endprogram
RESULTS:
# count_0 = 0 , count_1 = 0, count_2 = 3362, count_3 = 3321, count_4 = 3317, count_5 = 0,
count_6 = 0, count_7= 0
# count_0_1 = 0 ;count_2_7 = 10000
Calling a new(), resets the distrubution algorithem. If new() is called to creat an object when ever
randomized is called,the dist algorithm restarts and the distribution may not be what is expected.
EXAMPLE:
class Dist;
rand integer Var;
constraint range { Var dist { [0:1] := 50 , [2:7] := 50 }; }
endclass
program Dist_p_52;
Dist obj;
integer count_0_1 ,count_2_7 ;
initial
begin
obj=new();
count_0_1 = 0;count_2_7 = 0;
for(int i=0; i< 10000; i++)
begin
obj = new();
if( obj.randomize())
if( obj.Var inside {0,1} ) count_0_1 ++;
else if( obj.Var inside {[2:7]} ) count_2_7 ++;
end
$display("count_0_1 : %0d : count_2_7 : %0d ",count_0_1,count_2_7);
end
endprogram
RESULTS:
# count_0_1 : 3478 : count_2_7 : 6522
The distribution is not followed if the variable is declared as randc as distribution may require
repetition and randc doesnot allow. In the below example try changing the weights in distribution
function, you will get always same results otherwise compilation error.
EXAMPLE:
class Dist;
randc byte Var;
constraint range { Var dist { [0:1] := 50 , [2:7] := 50 }; }
endclass
program Dist_p_52;
Dist obj;
integer count_0_1 ,count_2_7 ;
initial
begin
obj=new();
count_0_1 = 0;count_2_7 = 0;
for(int i=0; i< 10000; i++)
begin
if( obj.randomize())
if( obj.Var inside {0,1} ) count_0_1 ++;
else if( obj.Var inside {[2:7]} ) count_2_7 ++;
end
$display("count_0_1 : %0d : count_2_7 : %0d ",count_0_1,count_2_7);
end
endprogram
To constraint enumerated values using dist, if they are less number, give weights individually.
If you want to give weights to enumerated values in groups, then give a continues group. As
enumeration is represented in integer, the discontinuation in the declaration may not result
properly.
EXAMPLE:
program enums_53;
typedef enum {V_SMALL,SMALL,MEDIUM,LARGE,V_LARGE} size_e;
class dist_c;
rand size_e size_d;
constraint size_dist{size_d dist {[V_SMALL:MEDIUM] :/40,[LARGE:V_LARGE] :/ 60}; }
endclass
initial begin
dist_c obj;
obj=new;
for (int i=0; i<=10; i++)
begin
obj.randomize();
$display (" size_d = %0s ", obj.size_d);
end
end
endprogram
Implication
Implication operator can be used to declare conditional relation. The syntax is expression ->
constraint set. If the expression is true,then the constraint solver should satisfy the constraint set.
If the expression is false then the random numbers generated are unconstrainted by constraint set.
The boolean equivalent of a -> b is (!a || b).
rand bit a;
rand bit [3:0] b;
constraint c { (a == 0) -> (b == 1); }
a is one and b is 4 bit, So there are total of 32 solutions. But due to constraint c (when ever a is 0
b should be 1) 15 solutions are removed. So probability of a = 0 is 1/(32-15) = 1/17. If u observe
the following program results count_0/count_1 approximately equal to 1/17.
EXAMPLE:
class impli;
rand bit a;
rand bit [3:0] b;
constraint c { (a == 0) -> (b == 1); }
endclass
program impli_p_54;
impli obj;
integer count_0 ,count_1 ;
initial
begin
obj=new();
count_0 = 0;count_1 = 0;
for(int i=0; i< 10000; i++)
begin
if( obj.randomize())
if( obj.a == 0 ) count_0 ++;
else count_1 ++;
end
$display(" count_0 = %0d;count_1 = %0d; ",count_0 ,count_1);
end
endprogram
RESULTS:
# count_0 = 571;count_1 = 9429;
If..Else
Just like implication, if...else style constraints are bidirectional.Above example applies hear too.
EXAMPLE:
class if_else;
rand bit a;
rand bit [3:0] b;
constraint c { if(a == 0) (b == 1); }
endclass
program if_else_p_55;
if_else obj;
integer count_0 ,count_1 ;
initial
begin
obj=new();
count_0 = 0;count_1 = 0;
for(int i=0; i< 10000; i++)
begin
obj = new();
if( obj.randomize())
begin
if( obj.a == 0 ) count_0 ++;
else if( obj.a == 1 ) count_1 ++;
end
end
$display(" count_0 = %0d;count_1 = %0d;",count_0 ,count_1);
end
endprogram
RESULTS:
EXAMPLE:
class Var_order_56;
rand bit a;
rand bit [3:0] b;
constraint bidirectional { a -> b == 0; }
endclass
The probability of a=1 is 1/((2**5)-15)=1/17, as constraints are bidirectional i.e both the values
of a and b are determined together. Constraints will be solved only once, the solver picks the one
solution from the possible set of {a,b} which has 17 solutions. To guide the probability of
selecting a= 0 to 50% and a = 1 to 50%, use
initial
begin
count_0 = 0;count_1 = 0;
for(int i=0; i< 10000; i++)
begin
obj = new();
if( obj.randomize())
if( obj.a == 0 ) count_0 ++;
else if( obj.a == 1 ) count_1 ++;
end
$display(" count_0 = %0d;count_1 = %0d;",count_0 ,count_1);
end
endprogram
RESULTS:
# count_0 = 4974;count_1 = 5026;
Too many explicit variable ordering may lead to circular dependency. The LRM says that
"Circular dependencies created by the implicit variable ordering shall result in an error." and
"circular dependency is not allowed". But it does not put restriction on what to do if a explicit
circular dependency exists. Check with your tool, if explicit Circular dependency is existing, it
may report warning,it may fail solver or proceed by just ignoring the order.
EXAMPLE:
program Cir_Dip_p_58;
class Cir_Dep;
rand integer a,b,c;
constraint a_c { solve a before b ;}
constraint b_c { solve b before c ;}
constraint c_c { solve c before a ;}
endclass
Cir_Dip obj=new();
initial
void'(obj.randomize());
endprogram
RESULTS:
Error: Cird.sv(14): Cyclical dependency between random variables specified by solve/before
constraints.
LRM says, if the outcome is same, solver can solve without following the rule. In the following
case, x has only one possible assignment (0), so x can be solved for before y. The constraint
solver can use this flexibility to speed up the solving process.
EXAMPLE:
class slove_before;
rand integer x,y;
constraint C {x == 0;
x < y;
solve y before x; }
endclass
program s_b_59;
slove_before obj ;
initial
begin
obj = new();
repeat(5)
if(obj.randomize())
$display(" x : %d :: y :%d ",obj.x,obj.y);
else
$display("Randomization failed ");
end
endprogram
RESULTS:
# x: 0 :: y : 2064490291
# x: 0 :: y : 2035140763
# x: 0 :: y : 1279931677
# x: 0 :: y : 2112945927
# x: 0 :: y : 1977312554
Functions
Functions are allowed in constraints. It will be useful in applications like constrain the max
packet size based on the bandwidth other parameters. Constraint statements are more error pron.
Functions in constraints are called before constraints are solved. The return value of the function
is used to solve the constraint and it is treated as state variable. There is an implicit variable
ordering when solving these constraints. Function should not be declared as static and should not
modify the constraints, for example calling the rand_mode and constraint_mode methods.
EXAMPLE:
class B_61;
rand int x, y;
constraint C { x <= F(y); }
constraint D { y inside { 2, 4, 8 } ; }
endclass
Random variables used as function arguments shall establish an implicit variable ordering or
priority. Constraints that include only variables with higher priority are solved before other lower
priority constraints. Random variables solved as part of a higher priority set of constraints,
become state variables to the remaining set of constraints. In above example y is solved before x.
If y is not rand variable, then F(y) is equalent to calling it in the pre_randomize method.
Iterative Constraints
Iterative constraints allow Constraining individual elements of fixed-size, dynamic, associative
arrays or queue. foreach construct specifies iteration over the each elements of array.
EXAMPLE:
class Eth_pkt_60;
rand byte Payload[] ;
constraint size_c { Payload.size() inside {[10:1500]}; }
constraint element_c { foreach ( Payload[ i ] ) Payload[ i ] inside {[50:100]}; }
function void post_randomize;
foreach(Payload[i])
$display( " Payload[ %0d ] :%d ",i,Payload[ i ] );
endfunction
endclass
program iterative_60;
Eth_pkt_60 obj;
initial
begin
obj = new();
if(obj.randomize())
$display(" RANDOMIZATION DONE ");
end
endprogram
RESULTS:
# Payload[ 0 ] : 87
# Payload[ 1 ] : 52
# Payload[ 2 ] : 70
# Payload[ 3 ] : 76
# Payload[ 4 ] : 71
# Payload[ 5 ] : 63
# Payload[ 6 ] : 62
# Payload[ 7 ] : 63
# Payload[ 8 ] : 66
# Payload[ 9 ] : 85
# Payload[ 10 ] : 95
# Payload[ 11 ] : 57
# Payload[ 12 ] : 78
NOTE: Remember while randomizing each element of array also array should be declared as
random variable otherwise randomization wont happen.
program constr_p_67;
constr_p_67 obj;
initial
begin
obj=new();
repeat(10000)
void'(obj.randomize());
$finish(2);
end
endprogram
RESULT:
# ** Note: Data structure takes 3407960 bytes of memory
# Process time 9.24 seconds
Run all the above three examples with your simulator and check how your simulation speed
varies.
When iterative constraints are used on arrays, each element has a constraint on it. If the
constraints are simple enough to implement with out using constraint block, simulation time may
be saved. In the following example, there are two constraints blocks, one for size other for each
element. In reality there may be more constraint blocks.
EXAMPLE:
class Eth_pkt;
rand byte Payload[] ;
constraint size_c { Payload.size() inside {[46:1500]}; }
constraint element_c { foreach ( Payload[ i ] ) Payload[ i ] inside {[50:100]}; }
endclass
program iterative_68;
Eth_pkt obj;
initial
begin
obj = new();
for(int i=0;i< 10000;i++)
begin
if(obj.randomize())
$display(" RANDOMIZATION DONE ");
end
$finish(2);
end
endprogram
RESULT:
# ** Note: Data structure takes 3407960 bytes of memory
# Process time 705.51 seconds
The above logic can implemented using post_randomize. Check how these two example with ur
vendor tool and look at the simulation speed. You may find difference.
EXAMPLE:
class Eth_pkt;
rand integer length;
byte Payload[] ;
constraint size_c { length inside {[46:1500]}; }
function void post_randomize;
Payload = new[length];
for(int i=0;i< length;i++)
Payload[ i ] = 50 + $urandom % 51 ;
endfunction
endclass
program iterative_69;
Eth_pkt obj;
initial
begin
obj = new();
for(int i=0;i< 10000;i++)
begin
if(obj.randomize())
$display(" RANDOMIZATION DONE ");
end
$finish(2);
end
endprogram
# ** Note: Data structure takes 3539032 bytes of memory
# Process time 3.92 seconds
RANDCASE
randcase is a case statement that randomly selects one of its branches. The randcase item
expressions are non-negative integral values that constitute the branch weights. An item weight
divided by the sum of all weights gives the probability of taking that branch. Randcase can be
used in class are modules. The randcase weights can be arbitrary expressions, not just constants.
EXAMPLE:
randcase
3 : x = 1;
1 : x = 2;
4 : x = 3;
endcase
The sum of all weights is 8; therefore, the probability of taking the first branch is 0.375, the
probability of taking the second is 0.125, and the probability of taking the third is 0.5. If a branch
specifies a zero weight, then that branch is not taken. The sum of all weights (SUM) is computed
(negative values contribute a zero weight). If SUM is zero or exceeds (2*32-1), no branch is
taken. Each call to randcae statement will return a random number in the range from 0 to SUM.
$urandom_range(0,SUM) is used to generate a random number. As the random numbers are
generated using $urandom are thread stable, randcase also exhibit random stability.
EXAMPLE:
module rand_case_70;
integer x;
integer cnt_1,cnt_2,cnt_3;
initial
begin
cnt_1 = 0;cnt_2=0;cnt_3 = 0;
repeat(100000)
begin
randcase
3 : x = 1;
1 : x = 2;
4 : x = 3;
endcase
if(x == 1)
cnt_1++;
else if(x == 2)
cnt_2++;
else if(x ==3)
cnt_3++;
end
$display("count_1 =%0d count_2 =%0d count_3 =%0d ",cnt_1,cnt_2,cnt_3);
$display("Probability of count_1 =%0f count_2 =%0f count_3 =%0f
",(cnt_1/100000.0),(cnt_2/100000.0),(cnt_3/100000.0));
end
endmodule
RESULTS:
# count_1 =37578 count_2 =12643 count_3 =49779
# Probability of count_1 =0.375780 count_2 =0.126430 count_3 =0.497790
RANDSEQUENCE
The random sequence generator is useful for randomly generating sequences of stimulus. For
example, to verify a temporal scenario, a sequence of packets are needed. By randomizing a
packet, it will generate most unlikely scenarios which are not interested. These type of sequence
of scenarios can be generated using randsequence. A randsequence grammar is composed of one
or more productions. Production items are further classified into terminals and nonterminals. A
terminal is an indivisible item that needs no further definition than its associated code block.
EXAMPLE:
module rs();
initial
begin
repeat(5)
begin
randsequence( main )
main : one two three ;
one : {$write("one");};
two : {$write(" two");};
three: {$display(" three");};
endsequence
end
end
endmodule
RESULTS:
one two three
one two three
one two three
one two three
one two three
The production main is defined in terms of three nonterminals: one, two and three. Productions
one,two and three are terminals. When the main is chosen, it will select the sequence one, two
and three in order.
Random Productions:
A single production can contain multiple production lists separated by the | symbol. Production
lists separated by a | imply a set of choices, which the generator will make at random.
EXAMPLE:
module rs();
initial
repeat(8)
randsequence( main )
main : one | two | three ;
one : {$display("one");};
two : {$display("two");};
three: {$display("three");};
endsequence
endmodule
RESULTS:
# three
# three
# three
# three
# one
# three
# two
# two
Results show that one, two and three are selected randomly.
Random Production Weights :
The probability that a production list is generated can be changed by assigning weights to
production lists. The probability that a particular production list is generated is proportional to its
specified weight. The := operator assigns the weight specified by the weight_specification to its
production list. A weight_specification must evaluate to an integral non-negative value. A weight
is only meaningful when assigned to alternative productions, that is, production list separated by
a |. Weight expressions are evaluated when their enclosing production is selected, thus allowing
weights to change dynamically.
EXAMPLE:
module rs();
integer one_1,two_2,three_3;
initial
begin
one_1 = 0;
two_2 = 0;
three_3 = 0;
repeat(6000)
randsequence( main )
main : one := 1| two := 2| three := 3;
one : {one_1++;};
two : {two_2++;};
three: {three_3++;};
endsequence
$display(" one %0d two %0d three %0d",one_1,two_2,three_3);
end
endmodule
RESULTS:
# one 1011 two 2005 three 2984
If..Else
A production can be made conditional by means of an if..else production statement. The
expression can be any expression that evaluates to a boolean value. If the expression evaluates to
true, the production following the expression is generated, otherwise the production following
the optional else statement is generated.
EXAMPLE:
module rs();
integer one_1,two_2,three_3;
reg on;
initial
begin
on = 0;
one_1 = 0;
two_2 = 0;
three_3 = 0;
repeat(2500)
randsequence( main )
main : one three;
one : {if(on) one_1++; else two_2 ++; };
three: {three_3++;};
endsequence
$display(" one %0d two %0d three %0d",one_1,two_2,three_3);
end
endmodule
RESULTS:
# one 0 two 2500 three 2500
Case
A production can be selected from a set of alternatives using a case production statement. The
case expression is evaluated, and its value is compared against the value of each case-item
expression, which are evaluated and compared in the order in which they are given. The
production associated with the first case-item expression that matches the case expression is
generated. If no matching case-item expression is found then the production associated with the
optional default item is generated, or nothing if there no default item. Case-item expressions
separated by commas allow multiple expressions to share the production.
EXAMPLE:
module rs();
integer one_1,two_2,three_3;
initial
begin
one_1 = 0;
two_2 = 0;
three_3 = 0;
for(int i = 0 ;i < 6 ;i++)
begin
randsequence( main )
main : case(i%3)
0 : one;
1 : two;
default: def;
endcase;
one : {$display("one");};
two : {$display("two");};
def : {$display("default");};
endsequence
end
end
endmodule
RESULTS:
one
two
default
one
two
default
Repeat Production Statements :
The repeat production statement is used to iterate a production over a specified number of times.
The repeat production statement itself cannot be terminated prematurely. A break statement will
terminate the entire randsequence block
RANDOM STABILITY
In verilog,if the source code does not change,with the same seed,the simulator producess the
same random stimulus on any mechine or any operating system.Verilog has only one Random
number generator.Random stimulus is generated using $random(seed) where the seed is input to
the RNG.$random will always return the same value for same seed.
EXAMPLE:
module seed_74();
initial
repeat(5)
$display("random stimuls is %d",$random(Seed);
endmodule
While debugging to produce the same simulation, we should make sure that calls to RNG is not
disturbed. In Verilog if source code changes it is very unlikely that same stimulus is produced
with the same seed and we miss the bug.
In SystemVerilog seeding will be done hierachily. Every module instance, interface instance,
program instance and package has initialization RNG. Every thread and object has independent
RNG . When ever dynamic thread is created its RNG is initialized with the next random value
from its parent thread. RNG initialization and RNG generation are different process.During RNG
initialization,only seed is set to RNG. When ever static thread is created its RNG is initialized
with the next random value from the initialization RNG of module instance, interface instance,
program interface or package containing thread declaration. RNG initialization and RNG
generation are different process. During RNG initialization,only seed is set to RNG.
The random number generator is deterministic. Each time the program executes, it cycles
through the same random sequence. This sequence can be made nondeterministic by seeding the
$urandom function with an extrinsic random variable, such as the time of day.
SystemVerilog system functions $urandom and $urandom_range are thread stable. Calls to RNG
using these system function, uses the RNG of that thread. So next time while using $random in
SystemVerilog,think twice.
NOTE: The same stimulus sequence can not be produced on different simulators as the LRM
does not restrict the vendors to impliment specific constraint solver. Verilog LRM specifies the
RNG algorithm for $random ,so the same stimulas can be produced on different simulators(not
always as I discussed in one of the above topic). Even if the SystemVerilog LRM specifies RNG
algorithm ,the same sequence cannot be produced on different vendors because of the following
are few reasons :
-> LRM doesnot restrict the constraint solver algorithm.
-> Order of threads creation.
-> Order of thread execution.
EXAMPLE:
class Ran_Stb_1;
rand bit [2:0] Var;
endclass
program Ran_Stb_p_75;
Ran_Stb_1 obj_1 = new();
initial
repeat(10)
begin
void'(obj_1.randomize());
$display(" Ran_Stb_1.Var : %0d ",obj_1.Var);
end
endprogram
RESULTS:
# Ran_Stb_1.Var : 4
# Ran_Stb_1.Var : 5
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 0
# Ran_Stb_1.Var : 2
# Ran_Stb_1.Var : 2
# Ran_Stb_1.Var : 7
# Ran_Stb_1.Var : 6
# Ran_Stb_1.Var : 0
Stimulus generated in a thread or object is independed of other stimulus. So changes in the
source code will not effect threads or objects. This is Valid as long as the order of the threads is
not distrubed. If a new object is created, make sure that that they are added at the end.
EXAMPLE:
class Ran_Stb_1;
rand bit [2:0] Var;
endclass
class Ran_Stb_2;
rand bit [2:0] Var;
endclass
program Ran_Stb_p_76;
Ran_Stb_1 obj_1 = new();
Ran_Stb_2 obj_2 = new();
initial
repeat(10)
begin
void'(obj_1.randomize());
$display(" Ran_Stb_1.Var : %0d ",obj_1.Var);
end
endprogram
New object obj_2 is added after all the objects. Look at the simulation results,they are same as
the above.
RESULTS:
# Ran_Stb_1.Var : 4
# Ran_Stb_1.Var : 5
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 0
# Ran_Stb_1.Var : 2
# Ran_Stb_1.Var : 2
# Ran_Stb_1.Var : 7
# Ran_Stb_1.Var : 6
# Ran_Stb_1.Var : 0
If a new thread is added, make sure that it is added after all the threads.
EXAMPLE:
class Ran_Stb_1;
rand bit [2:0] Var;
endclass
class Ran_Stb_2;
rand bit [2:0] Var;
endclass
program Ran_Stb_p_77;
Ran_Stb_1 obj_1 = new();
Ran_Stb_2 obj_2 = new();
initial
begin
repeat(5)
begin
void'(obj_1.randomize());
$display(" Ran_Stb_1.Var : %0d ",obj_1.Var);
end
repeat(5)
begin
void'(obj_2.randomize());
$display(" Ran_Stb_2.Var : %0d ",obj_2.Var);
end
end
endprogram
The results show clearly that Random values in obj_1 are same as previous program simulation.
RESULTS:
# Ran_Stb_1.Var : 4
# Ran_Stb_1.Var : 5
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 0
# Ran_Stb_2.Var : 3
# Ran_Stb_2.Var : 3
# Ran_Stb_2.Var : 6
# Ran_Stb_2.Var : 1
# Ran_Stb_2.Var : 1
Order of the randomize call to different objects doesnot effect the RNG generation.
EXAMPLE:
class Ran_Stb_1;
rand bit [2:0] Var;
endclass
class Ran_Stb_2;
rand bit [2:0] Var;
endclass
program Ran_Stb_p_78;
Ran_Stb_1 obj_1 = new();
Ran_Stb_2 obj_2 = new();
initial
begin
repeat(5)
begin
void'(obj_2.randomize());
$display(" Ran_Stb_2.Var : %0d ",obj_2.Var);
end
repeat(5)
begin
void'(obj_1.randomize());
$display(" Ran_Stb_1.Var : %0d ",obj_1.Var);
end
end
endprogram
RESULTS:
# Ran_Stb_2.Var : 3
# Ran_Stb_2.Var : 3
# Ran_Stb_2.Var : 6
# Ran_Stb_2.Var : 1
# Ran_Stb_2.Var : 1
# Ran_Stb_1.Var : 4
# Ran_Stb_1.Var : 5
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 0
Adding a constraint in one class, will only effect the stimuls of that object only.
EXAMPLE:
class Ran_Stb_1;
rand bit [2:0] Var;
constraint C { Var < 4 ;}
endclass
class Ran_Stb_2;
rand bit [2:0] Var;
endclass
program Ran_Stb_p_79;
Ran_Stb_1 obj_1 = new();
Ran_Stb_2 obj_2 = new();
initial
repeat(5)
begin
void'(obj_1.randomize());
void'(obj_2.randomize());
$display(" Ran_Stb_1.Var : %0d :: Ran_Stb_2.Var : %0d ",obj_1.Var,obj_2.Var);
end
endprogram
RESULTS:
# Ran_Stb_1.Var : 0 :: Ran_Stb_2.Var : 3
# Ran_Stb_1.Var : 1 :: Ran_Stb_2.Var : 3
# Ran_Stb_1.Var : 1 :: Ran_Stb_2.Var : 6
# Ran_Stb_1.Var : 1 :: Ran_Stb_2.Var : 1
# Ran_Stb_1.Var : 0 :: Ran_Stb_2.Var : 1
Srandom
When an object or thread is created, its RNG is seeded using the next value from the RNG of the
thread that creates the object. This process is called hierarchical object seeding. Sometimes it is
desirable to manually seed an objects RNG using the srandom() method. This can be done either
in a class method or external to the class definition. Example to demonstrate seeding in objects.
EXAMPLE:
class Rand_seed;
rand integer Var;
function new (int seed);
srandom(seed);
$display(" SEED is initised to %0d ",seed);
endfunction
function void post_randomize();
$display(": %0d :",Var);
endfunction
endclass
program Rand_seed_p_80;
Rand_seed rs;
initial
begin
rs = new(20);
repeat(5)
void'(rs.randomize());
rs = new(1);
repeat(5)
void'(rs.randomize());
rs = new(20);
repeat(5)
void'(rs.randomize());
end
endprogram
RESULTS:
# SEED is initised to 20
# : 1238041889 :
# : 1426811443 :
# : 220507023 :
# : -388868248 :
# : -1494908082 :
# SEED is initised to 1
# : 1910312675 :
# : 632781593 :
# : -453486143 :
# : 671009059 :
# : -967095385 :
# SEED is initised to 20
# : 1238041889 :
# : 1426811443 :
# : 220507023 :
# : -388868248 :
# : -1494908082 :
Simulation results show that same sequence is repeated when the same seed is used for
initialization.
Example to demonstrate seeding a thread.
EXAMPLE:
integer x, y, z;
fork //set a seed at the start of a thread
begin process::self.srandom(100); x = $urandom; end
//set a seed during a thread
begin y = $urandom; process::self.srandom(200); end
// draw 2 values from the thread RNG
begin z = $urandom + $urandom ; end
join
The above program fragment illustrates several properties:
Thread locality: The values returned for x, y, and z are independent of the order of thread
execution. This is an important property because it allows development of subsystems that are
independent, controllable, and predictable.
Hierarchical seeding: When a thread is created, its random state is initialized using the next
random value from the parent thread as a seed. The three forked threads are all seeded from the
parent thread.
IEEE 2005 SystemVerilog LRM does not specify whether scope randomization function is
random stable or not.
From LRM
13.13 Random stability
The RNG is localized to threads and objects. Because the sequence of random values returned by
a thread or object is independent of the RNG in other threads or objects, this property is called
random stability.
Random stability applies to the following:
In above its mentioned that "object randomization method, randomize()". There is nothing
mentioned about std::randomize() method random stability.
ARRAY RANDOMIZATION
Most application require to randomize elememts of array.Arrays are used to model payload,port
connections etc.
SystemVerilog has Fixed Arrays,Dynamic arrays,queues and Associative arrays.
If an array is constrained by both size constraints and iterative constraints for constraining every
element of array. The size constraints are solved first, and the iterative constraints next. In the
example,size_c is solved first before element_c. As constraint element_c canot be solved without
knowing the size. So there is implicit ordering while solveing this type of constraints.
This is the only approach for applying constraints to individual elements of arrays, with this
approach,performance is degraded. If Payload.size() = 1000, then 1000 constraints are created
by foreach. it takes much time for the solver to solve all the constraints. Another simple approach
is using a task to randomize each element. This approach is 5X times faster than foreach.
EXAMPLE:
class Eth_pkt_82;
rand byte Payload[] ;
constraint size_c { Payload.size() inside {[46:1500]}; }
task randomize_foreach;
foreach ( Payload[ i ] )
Payload[ i ] = 50 + $urandom % 50;// try with randomize( Payload[i]) with .....
endtask
endclass
program iterative;
Eth_pkt_82 obj;
initial
begin
obj = new();
for(int i=0;i< 10000;i++)
begin
void'(obj.randomize());
obj.randomize_foreach();
end
end
endprogram
In applications where some constraints are dependent on the array elements, the following may
help. call the randomization function in constraints, define proper variable ordering.
EXAMPLE:
class Eth_pkt;
rand byte Payload[] ;
rand byte size;
bit dummy = 1;
constraint dummy_c { dummy == randomize_size();}
constraint size_c { size inside {[10:100]};}
constraint order_c1 {solve size before dummy;}
constraint order_c2 {solve dummy before Payload;}
function bit randomize_size();
Payload = new[size];
randomize_size = 1;
return randomize_size;
endfunction
endclass
program iterative_88;
Eth_pkt obj;
initial
begin
obj = new();
void'(obj.randomize());
for(int i=0;i< 10;i++)
begin
void'(obj.randomize());
$display(" Eth_pkt.size : %d :: Payload.size() : %d ",obj.size,obj.Payload.size());
for(int j=0;j<obj.Payload.size();j++)
$write("%4d _",obj.Payload[j]);
$display("");
end
end
endprogram
In the above example, constraint order_c1 {solve size before dummy;} makes sure that size is
randomized before function ranomize_size() is called.
If constraints are dependent on the sum of the elements of the dynamic array. The interesting
elements are the new random variables which are created by current randomization call. If you
are not using any techinique to get the right size after randomization sum() returns the sum of all
the array elements .
EXAMPLE:
class dynamic_array_89;
rand byte size;
rand byte data[];
constraint size_c { data.size() == size; size >= 0; }
constraint sum_c { data.sum() < 1000;}
endclass
Do manually in a function and use it or just use { data.sum() with (item.index < size)
1000;}There is one more bug in the above code.The sum() method returns a single value of the
same type as the array element type.So the sum returns only 8 bits in this case.So a.sum() is
allways less than 255 which is less than 1000 and allways the constraint is satisfied which is not
what is expected.
EXAMPLE:
program summ;
dynamic_array obj = new();
integer sum;
initial
begin
sum =0;
void'(obj.randomize());
for(int i=0;i< obj.size ;i++)
sum= sum+obj.data[i];
$display(" Sum is %d ",sum);
end
endprogram
Using y.sum with (item + 32'b0) will result in a 32 bit proper sum.
EXAMPLE:
class dynamic_array;
rand integer size;
rand reg [7:0] data[];
constraint sum_c { data.sum() == data[0];}
constraint size_c { data.size() == size; size >1000 ;size < 2000; }
endclass
program summ;
dynamic_array obj = new();
integer sum;
initial
repeat(10)
begin
sum =0;
void'(obj.randomize());
for(int i=0;i< obj.size ;i++)
begin
sum= sum+obj.data[i];
end
$display(" Sum is %d obj.sum() %d",obj.data[0],obj.data.sum());
end
endprogram
Randomization donot create the objects. So when a array of objects is randomized, all the objects
are pointing to null and randomization can not be done on null objets.new() has to be called to
creat objects then only randomize can be done on it.So creat array of objects before calling the
randomize.
EXAMPLE:
class cls;
rand integer Var;
endclass
class arr_obj;
rand cls objcls [0:2];
endclass
program arr_obj_p_91;
arr_obj obj = new() ;
int i;
initial
begin
if(obj.randomize())
begin
$display(" Randomization is done ");
for(i=0;i<3;i++)
if(obj.objcls[i] == null )
$display( " obj.objcls == null ");
else
$display(" obj.objcls.Var : %d ", obj.objcls[i].Var );
end
else
$display(" Randomization failed ");
end
endprogram
RESULTS:
# Randomization is done
# obj.objcls == null
# obj.objcls == null
# obj.objcls == null
In the following, objects are created during the creation of arr_obj. This can also be done in
pre_randomize. When dynamic arrays of objects are created, similar approach has to be taken
and size of the dynamic array has to be decided before the new() is called, which makes no sense
using the dynamic array of objects.
EXAMPLE:
class cls;
rand integer Var;
endclass
class arr_obj;
rand cls objcls [0:2];
function new();
foreach(objcls[i])
objcls[i]=new();
endfunction
endclass
program arr_obj_p_91;
arr_obj obj = new() ;
int i;
initial
begin
if(obj.randomize())
begin
$display(" Randomization is done ");
for(i=0;i<3;i++)
if(obj.objcls[i] == null )
$display( " obj.objcls == null ");
else
$display(" obj.objcls.Var : %d ", obj.objcls[i].Var );
end
else
$display(" Randomization failed ");
end
endprogram
RESULTS:
Randomization is done
obj.objcls.Var : 733126180
obj.objcls.Var : -119008195
obj.objcls.Var : 342785185
To create queue of objects,first length of the queue has to be randomized.Then number of objects
equal to length of queue.Delete the old elements in the queue.Then push each object new objects
in to the queue.Lastly randomize each object.
EXAMPLE:
class cls;
rand integer Var;
endclass
class q_cls;
rand cls obj[$];
rand bit [2:0] length;
function void pre_randomize();
length = $urandom % 20 ; // Compute the length of queue.
obj = {} ; // Delet all the elements in the queue or .delet can be used
repeat(length)
begin
cls obj_loc;
obj_loc = new(); // Creat new object.
obj.Push_back(obj_loc) ; // insert it into queue.
end
endfunction
endclass
program q_obj_p_93;
q_cls obj = new();
initial
begin
if(obj.randomize())
begin
$display( "Randomization done");
$write( " Length of q : %0d :::",obj.length);
for(int i ;i< obj.length;i++)
begin
cls obj_temp;
obj_temp = obj.obj.Pop_front();
$write(" : %0d : ",obj_temp.Var);
end
end
else
$display( "Randomization failed");
end
endprogram
RESULT:
Randomization done
Length of q : 6 ::: : 1474208060 : : -1098913923 : : 816460770 : : 41501707 : : -1179418145
: : -212817600 :
Some application like linked list needs to generate an array of random values which are unique.
foreach provides the solution,but the simples and the best solution is assign all the elements in
arrat with its index and use shuffel inside a task.shuffle() randomizes the order of the elements in
the array.Constraint solver is not used hear,so the preformance is better.
EXAMPLE:
class List;
integer Pointer[5] ;
task randomize_unique;
foreach ( Pointer[ i ] )
Pointer[ i ] = i;
Pointer.shuffle();
endtask
endclass
program Unique_rand_94;
List obj;
initial
begin
obj = new();
obj.randomize_unique();
for(int i=0;i< 5;i++)
begin
$display(" Pointer[%0d] = %d ",i,obj.Pointer[i]);
end
end
endprogram
RESULT:
Pointer[0] = 2
Pointer[1] = 1
Pointer[2] = 3
Pointer[3] = 0
Pointer[4] = 4
Using foreach in global constraints in the following way wont work currently. as the .
operator(dot) is not supported in foreach in LRM currently.
EXAMPLE:
class parent;
rand byte a[0:9];
endclass
class child_96;
rand parent P;
rand byte a[0:9];
constraint Not_supported { foreach(P.a[i]) P.a[i] == a[i];}
endclass
The correct way to write the above constraint is
constraint Supported { foreach(a[i]) a[i] == P.a[i];}
CONSTRAINT GUARDS
Constraint guards are predicate expressions that function as guards against the creation of
constraints, and not as logical relations to be satisfied by the solver. These predicate expressions
&&,|| and ! are evaluated before the constraints are solved. This enables users to write constraints
that avoid errors due to nonexistent object handles or array indices out of bounds.
There are 4 states when a sub expression is evlauated.
0 FALSE Subexpression evaluates to FALSE.
1 TRUE Subexpression evaluates to TRUE.
E ERROR Subexpression causes an evaluation error like null pointer.
R RANDOM Expression includes random variables and cannot be evaluated.
If any of subexpression results ERROR,then randomization fails.
EXAMPLE:1
class SList_97;
rand int n;
rand Slist_97 next;
constraint sort { n < next.n; }
endclass
In the Example 1, while sorting the elements of array in ascending order, if next is null i.e for last
element randomization fails.
EXAMPLE:2
class SList_98;
rand int n;
rand Slist_98 next;
constraint sort { if( next != null ) n < next.n; }
endclass
In Example 2, Even if next is null, constraint wont be generated so randomization will never fail.
EXAMPLE:3
class D;
int x;
endclass
class C;
rand int x, y;
D a, b;
constraint c1 { (x < y || a.x > b.x || a.x == 5 ) -> x+y == 10; }
endclass
In Example 3, the predicate subexpressions are (x < y), (a.x > b.x), and (a.x == 5), which are all
connected by disjunction. Some possible cases are as follows:
Case 2: a is null.
This always results in error, irrespective of the other conditions.
Case 3: a is null
This always results in error, irrespective of the other conditions.
TITBITS
The first constraint program which wrote randomized sucessfully but the results are not what
expected.
My constraint is to limit Var between 0 and 100.
EXAMPLE:
class Base;
rand integer Var;
constraint randge { 0< Var < 100 ;}
endclass
program inhe_109;
Base obj;
initial
begin
obj = new();
for(int i=0 ; i < 100 ; i++)
if(obj.randomize())
$display(" Randomization successful : Var = %0d ",obj.Var);
else
$display("Randomization failed");
end
endprogram
RESULTS:
Randomization successful : Var = 2026924861
Randomization successful : Var = -1198182564
Randomization successful : Var = 1119963834
Randomization successful : Var = -21424360
Randomization successful : Var = -358373705
Randomization successful : Var = -345517999
Randomization successful : Var = -1435493197
..etc
The mistake what I have done is simple,this resulted the constraint solver to solve the statement
(((0 < Var) < 100))
For the above equation, are the results are correct.
Then I changed the constraint to { 0< Var ;Var < 100 ;}
The solver considered the this constraint as (0 < Var) && (Var < 100); and solution are correct.
To generate random values less then -10,the following may not work.
EXAMPLE:
class Base_110;
rand integer Var;
constraint randge { Var + 10 <= 0 ;}
endclass
RESULTS:
Randomization sucsessfull : Var = -1601810394
Randomization sucsessfull : Var = 2147483646
Randomization sucsessfull : Var = -335544322
Var = 2147483646 is not less -10, but the solver solved it using ((Var + 10) <= 0) i.e
((2147483646 + 10) <= 0) which is true
To solve this use the inside operator.
constraint randge { Var inside {[-10000:-10]};}
Make sure that constraint expression are not mixed up with signed and unsigned variables.
# Ran_Stb_1.Var : 4
# Ran_Stb_1.Var : 5
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 1
# Ran_Stb_1.Var : 0
# Ran_Stb_1.Var : 2
# Ran_Stb_1.Var : 2
# Ran_Stb_1.Var : 7
# Ran_Stb_1.Var : 6
# Ran_Stb_1.Var : 0
Constraining Non Integral Data Types:
Constraints can be any SystemVerilog expression with variables and constants of integral type
(e.g.,
bit, reg, logic, integer, enum, packed struct).
To Constraint a real number, randomize integer and convert it to real as it is required.
EXAMPLE:
class cls;
rand integer Var;
endclass
class real_c;
real r;
rand integer i;
rand integer j;
2.507685e-280
-1.188526e-07
9.658227e-297
-2.912335e+247
2.689449e+219
Saving Memory
In packet protocol application like PCI Express, the packets which are driven to the DUV has to
be manipulated and stored to compare with the actual packet which is coming from DUV. If the
packet size is large and number of packets are huge, it occupies more momery. In verilog in this
case the whole packet need to be stored. HVL has more advantages w.r.t this case. We can store
high level information like packet size, CRC error, header. But functional verification needs to
store the payload for checking that the payload did not get corrupted. Major part of the storage
taken by the payload itself. If we can avoid storing the payload, we can save lot of storage space.
The following technique assigns predictable random values to payload fields. Only the start of
the payload need to be stored.
EXAMPLE:
integer pkt_length;
byte payload[0:MAX];
task gen_payload(integer seed ,integer length);
integer temp_seed;
temp_seed = seed;
for(int i=0;i< length;i++)
begin
temp_seed = $random(temp_seed);
payload[i] = temp_seed;
end
endtask
This is the task which checks whether payload is recived didnot get corrupted.
EXAMPLE:
task check_payload(integer seed,integer length);
integer temp_seed;
temp_seed = seed;
for(int i=0;i< length;i++)
begin
temp_seed = $random(temp_seed);
if(payload[i] != temp_seed)
$display(" ERROR :: DATA MISMATCH ");
end
endtask
INTRODUCTION
Systemverilog Functional Coverage Features
COVER GROUP
The covergroup construct encapsulates the specification of a coverage model. Each covergroup
specification can include the following components:
A clocking event that synchronizes the sampling of coverage points
A set of coverage points
Cross coverage between coverage points
Optional formal arguments
Coverage options
A Cover group is defined between key words covergroup & endgroup.
A Covergroup Instance can be created using the new() operator.
covergroup cg;
...
...
...
endgroup
cg cg_inst = new;
The above example defines a covergroup named "cg". An instance of "cg" is declared as
"cg_inst" and created using the "new" operator.
SAMPLE
Coverage should be triggered to sample the coverage values. Sampling can be done using
Any event expression -edge, variable
End-point of a sequence
Event can be omitted
Calling sample() method.
covergroup cg @(posedge clk);
...
...
...
endgroup
The above example defines a covergroup named "cg". This covergroup will be automatically
sampled each time there is a posedge on "clk" signal.
covergroup cg;
...
...
...
endgroup
cg cg_inst = new;
initial // or task or function or always block
begin
...
...
cg_inst.sample();
...
...
end
Sampling can also be done by calling explicitly calling .sample() method in procedural code.
This is used when coverage sampling is required based on some calculations rather than events.
COVER POINTS
A covergroup can contain one or more coverage points. A coverage point can be an integral
variable or an integral expression. A coverage point creates a hierarchical scope, and can be
optionally labeled. If the label is specified then it designates the name of the coverage point.
program main;
bit [0:2] y;
bit [0:2] values[$]= '{3,5,6};
covergroup cg;
cover_point_y : coverpoint y;
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogramIn the above example, we are sampleing the cover point "y". The cover point is
named "cover_point_y" . In the Coverage report you will see this name. A cover group "cg" is
defined and its instance "cg_inst" is created. The value of "y" is sampled when cg_inst.sample()
method is called. Total possible values for Y are 0,1,2,3,4,5,6,7. The variable "y" is assigned
only values 3,5,6. The coverage engine should report that only 3 values are covered and there
are 8 possible values.
Commands To Simulate And Get The Coverage Report:
VCS
Compilation command:
vcs -sverilog -ntb_opts dtm filename.sv
Simulation Command:
./simv
Command to generate Coverage report: Coverage report in html format will be in the ./urgReport
directory
urg -dir simv.vdb
NCSIM
ncverilog -sv -access +rwc -coverage functional filename.sv
iccr iccr.cmd
iccr.cmd
load_test *
report_detail -instance -both -d *
QUESTASIM
Create the library
vlib library_name
Compilation command
vlog work library_name filename.sv
Simulation Command:
vsim library_name.module_top_name
Case 1) By default in modelsim.ini file to Name of UCDB file will be threre, If it is there, it will
create one file filename.ucdb
Case 2) Sometimes in modelsim.ini file UCDB File name will be commented in that case we
have to save UCDB File explicitly after vsim command
Coverage save filename.ucdb
Once u are ready with UCDB File u need to generate coverage report from ucdb file
To generate only Functional coverage report
vcover cvg myreport.txt outfilename.ucdb
After running the above program, the coverage report will show,
VARIABLE : cover_point_y
Expected : 8
Covered : 3
Percent: 37.50.
COVERPOINT EXPRESSION
Coverpoint Expression
A coverage point can be an integral variable or an integral Expression.
SystemVerilog allows specifying the cover points in various ways.
1)Using XMR
Example:
Cover_xmr : coverpoint top.DUT.Submodule.bus_address;
2)Part select
Example:
Cover_part: coverpoint bus_address[31:2];
3)Expression
Example:
Cocver_exp: coverpoint (a*b);
Example:
Cover_fun: coverpoint funcation_call();
5)Ref variable
Example:
covergroup (ref int r_v) cg;
cover_ref: coverpoint r_v;
endgroup
Coverage Filter
The expression within the iff construct specifies an optional condition that disables coverage for
that cover point. If the guard expression evaluates to false at a sampling point, the coverage point
is ignored.
For example:
covergroup cg;
coverpoint cp_varib iff(!reset); // filter condition
endgroup
In the preceding example, cover point varible "cp_varib" is covered only if the value reset is low.
int A, B;
rgc1 = new( A, 0, 50 );// cover A in range 0 to 50
rgc2 = new( B, 120, 600 );// cover B in range 120 to 600
The example above defines a coverage group, gc, in which the signal to be sampled as well as
the extent of the coverage bins are specified as arguments. Later, two instances of the coverage
group are created; each instance samples a different signal and covers a different range of values.
COVERAGE BINS
A coverage-point bin associates a name and a count with a set of values or a sequence of value
transitions. If the bin designates a set of values, the count is incremented every time the coverage
point matches one of the values in the set. If the bin designates a sequence of value transitions,
the count is incremented every time the coverage point matches the entire sequence of value
transitions.
Uncovered bins
------------------
auto[0:1]
Covered bins
------------------
auto[2:3]
auto[4:5]
auto[6:7]
Example of enum data type:
For Enum data type, the numbers of bins are equal to the number of elements of enum data type.
The bin identifiers are the enum member name.
typedef enum { A,B,C,D } alpha;
program main;
alpha y;
alpha values[$]= '{A,B,C};
covergroup cg;
cover_point_y : coverpoint y;
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In The above example, the variable "y" is enum data type and it can have 4 enum members
A,B,C and D. Variable Y is assigned only 3 Enum members A,B and C.
Coverage report:
---------------------
VARIABLE : cover_point_y
Expected : 4
Covered : 3
Percent: 75.00
Uncovered bins
--------------------
auto_D
Covered bins
--------------------
auto_C
auto_B
auto_A
EXPLICIT BIN CREATION
Explicit bin creation is recommended method. Not all values are interesting or relevant in a
cover point, so when the user knows the exact values he is going to cover, he can use explicit
bins. You can also name the bins.
program main;
bit [0:2] y;
bit [0:2] values[$]= '{3,5,6};
covergroup cg;
cover_point_y : coverpoint y {
bins a = {0,1};
bins b = {2,3};
bins c = {4,5};
bins d = {6,7};
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, bins are created explicitly. The bins are named a,b,c and d.
Coverage report:
-------------------
VARIABLE : cover_point_y
Expected : 4
Covered : 3
Percent: 75.00
Uncovered bins
--------------------
a
Covered bins
--------------------
b
c
d
Array Of Bins
To create a separate bin for each value (an array of bins) the square brackets, [], must follow the
bin name.
program main;
bit [0:2] y;
bit [0:2] values[$]= '{3,5,6};
covergroup cg;
cover_point_y : coverpoint y {
bins a[] = {[0:7]};
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, bin a is array of 8 bins and each bin associates to one number between 0
to 7.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 8
Covered : 3
Percent: 37.50
Uncovered bins
-------------------
a_0
a_1
a_2
a_4
a_7
Covered bins
-------------------
a_3
a_5
a_6
To create a fixed number of bins for a set of values, a number can be specified inside the square
brackets.
program main;
bit [0:3] y;
bit [0:2] values[$]= '{3,5,6};
covergroup cg;
cover_point_y : coverpoint y {
bins a[4] = {[0:7]};
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, variable y is 4 bit width vector. Total possible values for this vector are
16.
But in the cover point bins, we have giving the interested range as 0 to 7. So the coverage report
is calculated over the range 0 to 7 only. In this example, we have shown the number bins to be
fixed to size 4.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 4
Covered : 3
Percent: 75.00
Uncovered bins
-------------------
a[0:1]
Covered bins
------------------
a[2:3]
a[4:5]
a[6:7]
Default Bin
The default specification defines a bin that is associated with none of the defined value bins. The
default bin catches the values of the coverage point that do not lie within any of the defined bins.
However, the coverage calculation for a coverage point shall not take into account the coverage
captured by the default bin.
program main;
bit [0:3] y;
bit [0:2] values[$]= '{3,5,6};
covergroup cg;
cover_point_y : coverpoint y {
bins a[2] = {[0:4]};
bins d = default;
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, we have specified only 2 bins to cover values from 0 to 4. Rest of values
are covered in default bin <93>d<94> which is not using in calculating the coverage percentage.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 2
Covered : 1
Percent: 50.00
Uncovered bins
------------------
a[0:1]
Covered bins
----------------
a[2:4]
Default bin
-----------------
d
TRANSITION BINS
Transitional functional point bin is used to examine the legal transitions of a value.
SystemVerilog allows to specifies one or more sets of ordered value transitions of the coverage
point.
Type of Transitions:
covergroup cg;
cover_point_y : coverpoint y {
bins tran_34 = (3=>4);
bins tran_56 = (5=>6);
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, 2 bins are created for covering the transition of point "y" from 3 to 4 and
other for 5 to 6. The variable y is given the values and only the transition 5 to 6 is occurring.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 2
Covered : 1
Percent: 50.00
Uncovered bins
------------------
tran_34
Covered bins
----------------
tran_56
Sequence Of Transitions
A sequence of transitions is represented as:
value1 => value3 => value4 => value5
In this case, value1 is followed by value3, followed by value4 and followed by value5. A
sequence can be
of any arbitrary length.
program main;
bit [0:3] y;
bit [0:2] values[$]= '{3,5,6};
covergroup cg;
cover_point_y : coverpoint y {
bins tran_345 = (3=>4>=5);
bins tran_356 = (3=>5=>6);
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, 2 bins re created for covering the transition of point "y" from 3 to 4 to 5
and other for 3 to 5 to 6. The variable y is given the values and only the transition 3 to 5 to 6 is
occurring.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 2
Covered : 1
Percent: 50.00
Uncovered bins
------------------
tran_345
Covered bins
-----------------
tran_356
Set Of Transitions
program main;
bit [0:3] y;
bit [0:2] values[$]= '{3,5,6};
covergroup cg;
cover_point_y : coverpoint y {
bins trans[] = (3,4=>5,6);
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, bin trans creates 4 bin for covering 3=>5,4=>5,3=>6 and 4=>6.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 4
Covered : 1
Percent: 25.00
Uncovered bins
------------------
tran_34_to_56:3->6
tran_34_to_56:4->5
tran_34_to_56:4->6
Covered bins
----------------
tran_34_to_56:3->5
Consecutive Repetitions
Consecutive repetitions of transitions are specified using
trans_item [* repeat_range ]
covergroup cg;
cover_point_y : coverpoint y {
bins trans_3 = (3[*5]);
bins trans_4 = (4[*2]);
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 2
Covered : 1
Percent: 50.00
Uncovered bins
------------------
trans_3
Covered bins
----------------
trans_4
Range Of Repetition
covergroup cg;
cover_point_y : coverpoint y {
bins trans_3[] = (3[*3:5]);
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above example, only the sequence 3=>3=>3=>3 is generated. Other expected sequences
3=>3=>3 and 3=>3=>3=>3=>3 are not generated.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 3
Covered : 1
Percent: 33.33
Uncovered bins
------------------
tran_3:3[*3]
tran_3:3[*5]
Covered bins
----------------
tran_3:3[*4]
Goto Repetition
The goto repetition is specified using: trans_item [-> repeat_range]. The required number of
occurrences of a particular value is specified by the repeat_range. Any number of sample points
can occur before the first occurrence of the specified value and any number of sample points can
occur between each occurrence of the specified value. The transition following the goto
repetition must immediately follow the last occurrence of the repetition.
For example:
3 [-> 3]
is the same as
...=>3...=>3...=>3
where the dots (...) represent any transition that does not contain the value 3.
A goto repetition followed by an additional value is represented as follows:
1 => 3 [ -> 3] => 5
is the same as
1...=>3...=>3...=>3 =>5
program main;
bit [0:3] y;
bit [0:2] values[$]= '{1,6,3,6,3,6,3,5};
covergroup cg;
cover_point_y : coverpoint y {
bins trans_3 = (1=>3[->3]=>5);
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 1
Covered : 1
Percent: 100.00
Non Consecutive Repetition
The nonconsecutive repetition is specified using: trans_item [= repeat_range]. The required
number of occurrences of a particular value is specified by the repeat_range. Any number of
sample points can occur before the first occurrence of the specified value and any number of
sample points can occur between each occurrence of the specified value. The transition following
the nonconsecutive repetition may occur after any number of sample points so long as the
repetition value does not occur again.
For example:
3 [= 2]
is same as
...=>3...=>3
A nonconsecutive repetition followed by an additional value is represented as follows:
1 => 3 [=2] => 5
is the same as
1...=>3...=>3...=>5
program main;
bit [0:3] y;
bit [0:2] values[$]= '{1,6,3,6,3,6,5};
covergroup cg;
cover_point_y : coverpoint y {
bins trans_3 = (1=>3[=2]=>5);
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 1
Covered : 1
Percent: 100.00
WILDCARD BINS
By default, a value or transition bin definition can specify 4-state values.
When a bin definition includes an X or Z, it indicates that the bin count should only be
incremented when the sampled value has an X or Z in the same bit positions.
The wildcard bins definition causes all X, Z, or ? to be treated as wildcards for 0 or 1 (similar to
the ==? operator).
For example:
Covered bin
---------------
g12_15
Number of times g12_15 hit : 4
Similarly, transition bins can define wildcard bins.
For example:
The count of transition bin T0_3 is incremented for the following transitions (as if by
(0,1=>2,3)):
covergroup cg;
cover_point_y : coverpoint y {
wildcard bins trans = (2'b0X => 2'b1X );
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 1
Covered : 1
Percent: 100.00
Covered bin
---------------
trans
Number of times trans hit : 1 (01 => 10)
IGNORE BINS
A set of values or transitions associated with a coverage-point can be explicitly excluded from
coverage by specifying them as ignore_bins.
program main;
bit [0:2] y;
bit [0:2] values[$]= '{1,6,3,7,3,4,3,5};
covergroup cg;
cover_point_y : coverpoint y {
ignore_bins ig = {1,2,3,4,5};
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
In the above program, total possible values for y are 0 to 7. Ignore_bins specified to Ignored
values between 1 to 5. So the Expected values are 0,6 and 7. Out of these expected values, only 6
and 7 are generated.
Coverage report:
--------------------
VARIABLE : cover_point_y
Expected : 3
Covered : 2
Percent: 66.66
Uncovered bins
------------------
auto[0]
Excluded/Illegal bins
-------------------------
ig
auto[1]
auto[2]
auto[3]
auto[4]
auto[5]
Covered bins
----------------
auto[6]
auto[7]
ILLEGAL BINS
A set of values or transitions associated with a coverage-point can be marked as illegal by
specifying them as illegal_bins. All values or transitions associated with illegal bins are
excluded from coverage. If an illegal value or transition occurs, a runtime error is issued.
program main;
bit [0:2] y;
bit [0:2] values[$]= '{1,6,3,7,3,4,3,5};
covergroup cg;
cover_point_y : coverpoint y {
illegal_bins ib = {7};
}
endgroup
cg cg_inst = new();
initial
foreach(values[i])
begin
y = values[i];
cg_inst.sample();
end
endprogram
Result:
------------
** ERROR **
Illegal state bin ib of coverpoint cover_point_y in
covergroup cg got hit with value 0x7
CROSS COVERAGE
Cross allows keeping track of information which is received simultaneous on more than one
cover point. Cross coverage is specified using the cross construct.
program main;
bit [0:1] y;
bit [0:1] y_values[$]= '{1,3};
bit [0:1] z;
bit [0:1] z_values[$]= '{1,2};
covergroup cg;
cover_point_y : coverpoint y ;
cover_point_z : coverpoint z ;
cross_yz : cross cover_point_y,cover_point_z ;
endgroup
cg cg_inst = new();
initial
foreach(y_values[i])
begin
y = y_values[i];
z = z_values[i];
cg_inst.sample();
end
endprogram
In the above program, y has can have 4 values 0,1,2 and 3 and similarly z can have 4 values 0,1,2
and 3. The cross product of the y and z will be 16 values
(00),(01),(02),(03),(10),(11)........(y,z)......(3,2)(3,3) .
Only combinations (11) and (32) are generated.
Cross coverage report: cover points are not shown.
Covered bins
-----------------
cover_point_y cover_point_z
auto[3] auto[2]
auto[1] auto[1]
User-Defined Cross Bins
User-defined bins for cross coverage are defined using bin select expressions.
int i,j;
covergroup ct;
coverpoint i { bins i[] = { [0:1] }; }
coverpoint j { bins j[] = { [0:1] }; }
x1: cross i,j;
x2: cross i,j {
bins i_zero = binsof(i) intersect { 0 };
}
endgroup
COVERAGE OPTIONS
Options control the behavior of the covergroup, coverpoint, and cross.
There are two types of options:
Description :
If set at the covergroup syntactic level, it specifies the weight of this covergroup instance for
computing the overall instance coverage of the simulation. If set at the coverpoint (or cross)
syntactic level, it specifies the weight of a coverpoint (or cross) for computing the instance
coverage of the enclosing covergroup. The specified weight shall be a non-negative integral
value.
Goal
Syntax :goal=number
default value: 100
Description :
Specifies the target goal for a covergroup instance or for a coverpoint or a cross of an instance.
Name
Syntax :name=string
default value:unique name
Description :
Specifies a name for the covergroup instance. If unspecified, a unique name for each instance is
automatically generated by the tool.
Comment
Syntax :comment=string
default value: ""
Description :
A comment that appears with a covergroup instance or with a coverpoint or cross of the
covergroup instance. The comment is saved in the coverage database and included in the
coverage report.
At_least
Syntax :at_least=number
default value: 1
Description :
Minimum number of hits for each bin. A bin with a hit count that is less than number is not
considered covered.
Detect_overlap
Syntax :detect_overlap=Boolean
default value: 0
Description :
When true, a warning is issued if there is an overlap between the range list (or transition list) of
two bins of a coverpoint.
Auto_bin_max
Syntax :auto_bin_max=number
default value: 64
Description :
Maximum number of automatically created bins when no bins are explicitly defined for a
coverpoint.
Cross_num_print_missing
Syntax :cross_num_print_missing=number
default value: 0
Description :
Number of missing (not covered) cross product bins that shall be saved to the coverage database
and printed in the coverage report.
Per_instance
Syntax :per_instance=Boolean
default value: 0
Description :
Each instance contributes to the overall coverage information for the covergroup type. When
true, coverage information for this covergroup instance shall be saved in the coverage database
and included in the coverage report. When false, implementations are not required to save
instance-specific information.
Get_inst_coverage
Syntax :get_inst_coverage=Boolean
default value: 0
Description :
Only applies when the merge_instances type option is set . Enables the tracking of per instance
coverage with the get_inst_coverage built-in method. When false, the value returned by
get_inst_coverage shall equal the value returned by get_coverage
Following Table summarizes the syntactical level (covergroup, coverpoint, or cross) in which
type options can be specified.
COVERAGE METHODS
The following coverage methods are provided for the covergroup. These methods can be invoked
procedurally at any time.
void sample():
Description : Triggers sampling of the covergroup
real get_coverage()
real get_coverage(ref int, ref int)
Description : Calculates type coverage number (0...100)
The get_coverage() method returns the cumulative (or type) coverage, which considers the
contribution of all instances of a particular coverage item. and it is a static method that is
available on both types (via the :: operator) and instances (using the "." operator).
The get_coverage() method both accept an optional set of arguments, a pair of int values passed
by reference. When the optional arguments are specified, the get_coverage() method assign to
the first argument the value of the covered bins, and to the second argument the number of bins
for the given coverage item. These two values correspond to the numerator and the denominator
used for calculating the particular coverage number (i.e., the return value before scaling by 100).
real get_inst_coverage()
real get_inst_coverage(ref int, ref int)
Description : Calculates the coverage number (0...100)
get_inst_coverage() method returns the coverage of the specific instance on which it is invoked,
thus, it can only be invoked via the "." operator.
The get_inst_coverage() method both accept an optional set of arguments, a pair of int values
passed by reference. When the optional arguments are specified, the get_inst_coverage() method
assign to the first argument the value of the covered bins, and to the second argument the number
of bins for the given coverage item. These two values correspond to the numerator and the
denominator used for calculating the particular coverage number (i.e., the return value before
scaling by 100).
void set_inst_name(string)
Description : Sets the instance name to the given string
void start()
Description : Starts collecting coverage information
void stop()
Description : Stops collecting coverage information
SYSTEM TASKS
SystemVerilog provides the following system tasks and functions to help manage coverage data
collection.
$set_coverage_db_name ( name ) :
Sets the filename of the coverage database into which coverage information is saved at the end
of a simulation run.
$load_coverage_db ( name ) :
Load from the given filename the cumulative coverage information for all coverage group types.
$get_coverage ( ) :
Returns as a real number in the range 0 to 100 the overall coverage of all coverage group types.
This number is computed as described above.
COVER PROPERTY
Cover statement can be used to monitor sequences and other behavioral aspects of the design.
The tools can gather information about the evaluation and report the results at the end of
simulation. When the property for the cover statement is successful, the pass statements can
specify a coverage function, such as monitoring all paths for a sequence. The pass statement
shall not include any concurrent assert, assume or cover statement. A cover property creates a
single cover point.
Coverage results are divided into two: coverage for properties, coverage for sequences.
For sequence coverage, the statement appears as:
Cover property ( sequence_expr ) statement_or_null
Cover Property Results
The results of coverage statement for a property shall contain:
Number of times attempted
Number of times succeeded
Number of times failed
Number of times succeeded because of vacuity
Vacuity rules are applied only when implication operator is used. A property succeeds non-
vacuously only if the consequent of the implication contributes to the success.
Cover Sequence Results
In addition, statement_or_null gets executed for every match. If there are multiple matches at the
same time, the statement gets executed multiple times, one for each match.
It is recommended to cover sequences and not properties and its easy to convert a property into a
sequence if required.
Coverage property can be declared in
Cover groups are most useful at a higher level of abstractions where as cover property makes
sense to use when we want to work at low level signals.
We can mix cover group and cover property to gain the OO and temporal advantages. Using
properties for temporal expressions and trigger the cover group.
INTRODUCTION
SystemVerilog adds features to specify assertions of a system. An assertion specifies a behavior
of the system. Assertions are primarily used to validate the behavior of a design. In addition,
assertions can be used to provide functional coverage and generate input stimulus for validation.
The evaluation of the assertions is guaranteed to be equivalent between simulation, which is
event-based, and formal verification, which is cycle-based.
SystemVerilog allows assertions to communicate information to the test bench and allows the
test bench to react to the status of assertions without requiring a separate application
programming interface (API) of any kind
Advantages Of Assertion:
Improving Observability.
Reduces the debug time.
Bugs can be found earlier and are more isolated.
Controllable severity level.
Can interact with C functions.
Describe the Documentation and Specification of the design.
What Assertions Can Verify:
Assertions can be used to capture the information about various level of properties.
conceptual : can be used to verify systemlevel properties which are more architectural level.
design : These expresses unit level properties.
programming: More specified at RTL level.
1)conditional: It checks the some conditinal to be true using boolean expressions.
2)sequence : Checks whether the properties arr true using temporal expression.
3)signal : Checks on signal types.
a)x detection :Can be used to detect unconnected ports or undriven signal.
b)encoding types: Checks whether the encoding is violated.
1)onehot
2)gray code
EVENT SIMULATION
The Problem of race condition exists if the SVA is sampling the signals exactly at specified
event. So in SystemVerilog , the language has very clear semantics to avoid race condition while
evaluating SVA.
In SV, a new region is added before Active region called preponed. So sampling for SVA is
done in preponed region. No assignments are not done in preponed region. Signals are stable
from previous timeslot and they are occurring before active and NBA ,so the race condition is
avoided by this new preponed region. Look at the diagram, regions which are in light cyan color
are for SVA.
IN preponed region only sampling is done , the evaluation of these sampled values are done in
another region called observed region. Observed region occurs after NBA region. Even though
the assignments are done in active ,inactive,NBA region, these updates are not used in observed
region. Only signals sample in preponed region are used in observed region. Observed region
occurs before reactive region where the testbench executes.
But in immediate assertions, the updated values in previous regions of current time slot are used
in observed region.
ASSERTION TYPES
There are two types of assertions.
Immediate assertions are useful for checking combinational expression. These are similar to if
and else statement but with assertion control. Immediate assertions follow simulation event
semantics for their execution and are executed like a statement in a procedural block.
EXAMPLE:
time t;
always @(posedge clk)
if (state == REQ)
assert (req1 || req2);
else begin
t = $time;
#5 $error("assert failed at time %0t",t);
end
$assertoff shall stop the checking of all specified assertions until a subsequent $asserton. An
assertion that is already executing, including execution of the pass or fail statement, is not
affected.
$assertkill shall abort execution of any currently executing specified assertions and then stop the
checking of all specified assertions until a subsequent $asserton.
$asserton shall reenable the execution of all specified assertions. When invoked with no
arguments, the system task shall apply to all assertions. When the task is specified with
arguments, the first argument indicates levels of the hierarchy, consistent with the corresponding
argument to the Verilog $dumpvars system task. Subsequent arguments specify which scopes of
the model to control. These arguments can specify entire modules or individual assertions.
These layering concept allows to build hierarchical constructs so its easy to maintain them.
Boolean Expressions:
Boolean expression doesn't consume time. The result of the expression is 1,0,x & z. If the result
is 1, then the expression is true , else if the expression is 0,x or z , it is false. Concurrent
assertions use boolean expressions along with temporal expressions. Immediate assertions use
only boolean expressions.
EXAMPLE:
req ##2 gnt
This specifies that req shall be true on the current clock tick, and gnt shall be true on the second
subsequent clock tick
Zero Delay:
The delay ##0 indicates that the beginning of the second sequence is at the same clock tick as the
end of the first sequence. This can also be archived using boolean expressions && .
EXAMPLE:
sig1 ##0 sig2
This specifies that sig1 shall be true on the current clock tick, and sig2 shall be true on the same
clock tick.
Constant Range Delay:
A ##[n:m] followed by a range specifies the delay from the current clock tick to the beginning of
the sequence that follows.
EXAMPLE:
req ##[2:5] ack
This specifies that ack shall be true ,2-5 cycles after req. It creates multiple subsequence threads.
This resulst in multiple hits or fails.
Sub sequences created by range delay in above expressions:
req ##2 ack
req ##3 ack
req ##4 ack
req ##5 ack
Repetation Operators:
The number of iterations of a repetition can either be specified by exact count or be required to
fall within a finite range. If specified by exact count, then the number of iterations is defined by a
non-negative integer constant expression. If required to fall within a finite range, then the
minimum number of iterations is defined by a non-negative integer constant expression; and the
maximum number of iterations either is defined by a non-negative integer constant expression or
is $, indicating a finite, but unbounded, maximum.
Consecutive Repetition:
Consecutive repetition specifies finitely many iterative matches of the operand sequence, with a
delay of one clock tick from the end of one match to the beginning of the next. The overall
repetition sequence matches at the end of the last iterative match of the operand.
EXAMPLE:
REQ[*4]
This example specifies that ack shell come after req comes 4 times consecutively.
Goto Repetition :
Goto repetition specifies finitely many iterative matches of the operand boolean expression, with
a delay of one or more clock ticks from one match of the operand to the next successive match
and no match of the operand strictly in between. The overall repetition sequence matches at the
last iterative match of the operand.
EXAMPLE:
req[->3]##1 ack
This example specifies that ack shell come after req comes 3 times with no gap between thw last
req and ack.
EXAMPLE:
req[=3]##1 ack
This example specifies that ack shell come after req comes 4 times with gap between thw last req
and ack.
Sequence And :
Sequence must start and can end at the any time. Match is done after last sequence is ended.
EXAMPLE:
Seq1 and seq2
Sequence Or:
Sequence must start at the same time and can end at any time. Match is done at both the
sequences ends.
EXAMPLE:
seq1 or seq2
Sequence Intersect:
sequences must start at the same time and end at same tine. Match is done at the end time.
EXAMPLE:
Seq1 intersect seq2
Sequence Within
EXAMPLE:
Seq1 within seq2
Sequence First_match:
Sequence Throughout
Sequence Ended:
The end point of a sequence is reached whenever the ending clock tick of a match of the
sequence is reached, regardless of the starting lock tick of the match. The reaching of the end
point can be tested by using the method ended.
EXAMPLE:
sequence e1;
@(posedge sysclk) $rose(ready) ##1 proc1 ##1 proc2 ;
endsequence
sequence rule;
@(posedge sysclk) reset ##1 inst ##1 e1.ended ##1 branch_back;
endsequence
Operator precedence and associativity are listed in the following Table . The highest precedence
is listed first.
PROPERTIES
A property defines a behavior of the design. A property can be used for verification as an
assumption, a checker, or a coverage specification.
Sequences are often used to construct properties. usage of sequences in properties brakes down
the complexity. Sequence can be reused across various properties.
VERIFICATION DIRECTIVE
A property on its own is never evaluated for checking an expression. It must be used within a
verification statement for this to occur. A verification statement states the verification function to
be performed on the property.
The statement can be one of the following:
1)assert to specify the property as a checker to ensure that the property holds for the design
2)assume to specify the property as an assumption for the environment
3)cover to monitor the property evaluation for coverage
Assert: The assert statement is used to enforce a property as a checker. When the property for
the assert statement is evaluated to be true, the pass statements of the action block are executed.
Otherwise, the fail statements of the action_block are executed.
EXAMPLE:
a1_assertion:assert property ( @(posedge clk) req inside {0, 1} ) ;
property proto_assertion ;
@(posedge clk) req |-> req[*1:$] ##0 ack;
endproperty
Assume:
The environment must be constrained so that the properties that are assumed shall hold. Like an
assert property, an assumed property must be checked and reported if it fails to hold. There is no
requirement on the tools to report successes of the assumed properties.
EXAMPLE:
a1:assume property ( @(posedge clk) req dist {0:=40, 1:=60} ) ;
property proto ;
@(posedge clk) req |-> req[*1:$] ##0 ack;
endproperty
Cover Statement:
To monitor sequences and other behavioral aspects of the design for coverage, the same syntax is
used with the cover statement. The tools can gather information about the evaluation and report
the results at the end of simulation. When the property for the cover statement is successful, the
pass statements can specify a coverage function, such as monitoring all paths for a sequence. The
pass statement shall not include any concurrent assert, assume, or cover statement.
Coverage results are divided into two categories: coverage for properties and coverage for
sequences.
Coverage for sequences:
Number of attempts
Number of matches
Multiple matches per attempt are all counted
Coverage for properties:
Number of attempts
Number of passes
Number of vacuous passes
Number of failures
Expect Statement:
The expect statement is a procedural blocking statement that allows waiting on a property
evaluation. The syntax of the expect statement accepts a named property or a property
declaration. The expect statement accepts the same syntax used to assert a property. An expect
statement causes the executing process to block until the given property succeeds or fails. The
statement following the expect is scheduled to execute after processing the Observe region in
which the property completes its evaluation. When the property succeeds or fails, the process
unblocks, and the property stops being evaluated
EXAMPLE:
program tst;
initial begin
# 200ms;
expect( @(posedge clk) a ##1 b ##1 c ) else $error( "expect failed" );
ABC: ...
end
endprogram
Binding:
To facilitate verification separate from design, it is possible to specify properties and bind them
to specific modules or instances. The following are some goals of providing this feature:
It allows verification engineers to verify with minimum changes to the design code and files.
It allows a convenient mechanism to attach verification intellectual Protocol (VIP) to a module
or an instance.
No semantic changes to the assertions are introduced due to this feature. It is equivalent to
writing properties external to a module, using hierarchical path names.
From long time , Users have needes a simple way of communication to foreign languages from
verilog. VPI and PLI are not easy interfaces to Use . Users need detailed knowledge of PLI and
VPI even for a simple program. Most of the time, users do not need the sophisticated capabilities
of VPI and PLI. DPI also permits C/C++ code to wait for Verilog events and C/C++ tasks and
functions can be disabledfrom SystemVerilog.
SystemVerilog introduces a new foreign language interface called the Direct Programming
Interface (DPI). The DPI provides a very simple, straightforward, and efficient way to connect
SystemVerilog and foreign language code unlike PLI or VPI.
DPI consists of two separate layers: the SystemVerilog layer and a foreign language layer. Both
sides of DPI-C are fully isolated. Which programming language is actually used as the foreign
language is transparent and irrelevant for the System-Verilog side of this interface. Neither the
SystemVerilog compiler nor the foreign language compiler is required to analyze the source code
in the others language. Different programming languages can be used and supported with the
same intact SystemVerilog layer.
DPI-C follows the principle of a black box: the specification and the implementation of a
component are clearly separated, and the actual implementation is transparent to the rest of the
system. Therefore, the actual programming language of the implementation is also transparent,
although this standard defines only C linkage semantics. The separation between SystemVerilog
code and the foreign language is based on using functions as the natural encapsulation unit in
SystemVerilog.
LAYERS
Two Layers Of Dpi-C
DPI-C consists of two separate layers: the SystemVerilog layer and a foreign language layer. The
SystemVerilog layer does not depend on which programming language is actually used as the
foreign language. Although different programming languages can be supported and used with the
intact SystemVerilog layer, SystemVerilog defines a foreign language layer only for the C
programming language. Nevertheless, SystemVerilog code shall look identical and its semantics
shall be unchanged for any foreign language layer.
Dpi-C Systemverilog Layer
The SystemVerilog side of DPI-C does not depend on the foreign programming language. In
particular, the actual function call protocol and argument passing mechanisms used in the foreign
language are transparent and irrelevant to SystemVerilog. SystemVerilog code shall look
identical regardless of what code the foreign side of the interface is using. The semantics of the
SystemVerilog side of the interface is independent from the foreign side of the interface.
The SystemVerilog DPI-C allows direct inter-language function calls between SystemVerilog
and any foreign programming language with a C function call protocol and linking model:
Tasks implemented in SystemVerilog and specified in export declarations can be called from
C; such functions are referred to as exported tasks.
Functions implemented in C that can be called from SystemVerilog and can in turn call
exported tasks; such functions are referred to as imported tasks.
Dpi-C Foreign Language Layer
The foreign language layer of the interface (which is transparent to SystemVerilog) shall specify
how actual arguments are passed, how they can be accessed from the foreign code, how
SystemVerilog-specific data types (such as logic and packed) are represented, and how they are
translated to and from some predefined C-like types.
IMPORT
Import Methods
Methods implemented in C and given import declarations in SystemVerilog can be called from
SystemVerilog, such methods are referred to as imported methods.Imported tasks or functions
are similar to SystemVerilog tasks or functions. Imported tasks or functions can have zero or
more formal input, output, and inout arguments.
Imported tasks always return an int result as part of the DPI-C disable protocol and, thus, are
declared in foreign code as int functions. We will discusses about the DPI-C disable protocol in
following sections.
Imported functions can return a result or be defined as void functions.
In SV Code
Step1 : Import the C function
initial
begin
string_sv2c();
end
In C code:
Step3: Define the Imported function
void string_sv2c(){
printf(" C: Hellow from C ");
}
Full Example:
CODE: SV_file
program main;
string str;
initial
begin
string_sv2c();
end
endprogram
CODE: C_file
#include "svdpi.h"
void string_sv2c(){
printf(" C: Hellow from C ");
}
RESULTS
C: Hellow from C
Standard C Functions
EXAMPLE:
import "DPI" function chandle malloc(int size);
import "DPI" function void free(chandle ptr);
NAMING
Global Name
Every task or function imported to SystemVerilog must eventually resolve to a global symbol.
Similarly, every task or function exported from SystemVerilog defines a global symbol. Global
names of imported and exported tasks and functions must be unique (no overloading is allowed )
and shall follow C conventions for naming; specifically, such names must start with a letter or
underscore, and they can be followed by alphanumeric characters or underscores.
EXAMPLE
export "DPI-C" foo_plus = function \foo+ ; // "foo+" exported as "foo_plus"
import "DPI-C" init_1 = function void \init[1] (); // "init_1" is a linkage name
Local Name
If a global name is not explicitly given, it shall be the same as the SystemVerilog task or function
name.
EXAMPLE:
export "DPI-C" function foo;
If a C method is named same as the SystemVerilog Keyword, then use a leading backslash ( \ )
character to create the linkage identifier.
EXAMPLE:
import "DPI-C" \begin = function void \init[2] (); // "begin" is a linkage name
EXPORT
Export Methods
Methods implemented in SystemVerilog and specified in export declarations can be called from
C, such methods are referred to as exported methods.
Steps To Write Export Methods
In SV Code :
Setp1: Export the systemverilog function
void import_func()
{
export_func();
}
Full Example:
CODE: SV_file.sv
program main;
initial
begin
import_func();
end
endprogram
CODE: C_file.c
#include "stdio.h"
#include "vc_hdrs.h"
#include "svdpi.h"
RESULTS:
task export_task();
$display("SV: Entered the export function . wait for some time : %0d ",$time);
#100;
$display("SV: After waiting %0d",$time);
endtask
initial
begin
$display("SV: Before calling import function %0d",$time);
import_task();
$display("SV: After calling import function %0d",$time);
end
endprogram
CODE: C_file.c
extern void export_task();
void import_task()
{
printf(" C: Before calling export function\n");
export_task();
printf(" C: After calling export function\n");
}
RESULTS
SV: Before calling import function 0
C: Before calling export function
SV: Entered the export function . wait for some time : 0
SV: After waiting 100
C: After calling export function
SV: After calling import function 100
Pure Function
A function whose result depends solely on the values of its input arguments and with no side
effects can be specified as pure. This can usually allow for more optimizations and thus can
result in improved simulation performance.
A pure function call can be safely eliminated if its result is not needed or if the previous result
for the same values of input arguments is available somehow and can be reused without needing
to recalculate. Only nonvoid functions with no output or inout arguments can be specified as
pure.
Specifically, a pure function is assumed not to directly or indirectly (i.e., by calling other
functions) perform the following:
Perform any file operations.
Read or write anything in the broadest possible meaning, including input/output, environment
variables, objects from the operating system or from the program or other processes, shared
memory, sockets, etc.
Access any persistent data, like global or static variables.
Context Function
Some DPI imported tasks or functions or other interface functions called from them require that
the context of their call be known. The SystemVerilog context of DPI export tasks and functions
must be known when they are called, including when they are called by imports. When an import
invokes the svSetScope utility prior to calling the export, it sets the context explicitly. Otherwise,
the context will be the context of the instantiated scope where the import declaration is located.
CODE: SV_file_1.sv
module module_1;
endmodule
CODE: SV_file_2.sv
module module_2;
initial
import_func();
endmodule
CODE:C_file.c
#include "svdpi.h"
#include "stdio.h"
void import_func()
{
printf(" C: Im called fronm Scope :: %s \n\n ",svGetNameFromScope(svGetScope() ));
export_func();
}
RESULTS
DATA TYPES
The SystemVerilog DPI supports only SystemVerilog data types, which are the data types that
can cross the boundary between SystemVerilog and a foreign language in both the direction. On
the other hand, the data types used in C code shall be C types. A value that is passed through the
DPI is specified in SystemVerilog code as a value of SystemVerilog data type, while the same
value is declared C code as a value of C data type. Therefore, a pair of matching type definitions
is required to pass a value through DPI, the SystemVerilog definition and the C definition.
The following SystemVerilog types are the only permitted types for formal arguments of import
and export tasks or functions:
void, byte, shortint, int, longint, real, shortreal, chandle, and string
Scalar values of type bit and logic
Packed arrays, structs, and unions composed of types bit and logic. Every packed type is
eventually equivalent to a packed one-dimensional array. On the foreign language side of the
DPI, all packed types are perceived as packed one-dimensional arrays regardless of their
declaration in the SystemVerilog code.
Enumeration types interpreted as the type associated with that enumeration
Types constructed from the supported types with the help of the constructs: struct , union ,
Unpacked array , typedef
CODE:SV_file.sv
program main;
logic a;
import "DPI" function void show(logic a);
initial begin
a = 1'b0;
show(a);
a = 1'b1;
show(a);
a = 1'bX;
show(a);
a = 1'bZ;
show(a);
end
endprogram
CODE: C_file.v
#include <stdio.h>
#include <svdpi.h>
}
RESULTS
a is 0
a is 1
a is z
a is x
ARRAYS
Open Arrays
The size of the packed dimension, the unpacked dimension, or both dimensions can remain
unspecified,such cases are referred to as open arrays (or unsized arrays). Open arrays allow the
use of generic code to handle different sizes. Formal arguments in SystemVerilog can be
specified as open arrays solely in import declarations, exported. SystemVerilog functions cannot
have formal arguments specified as open arrays.
OpenArrays are good for generic programming, since C language doesn't have concept of
parameterizable arguments. Standared query and library functions are provided to determine
array information to acess array elements.
EXAMPLE: open arrays
CODE:SV_file.sv
program main;
int fxd_arr_1[8:3];
int fxd_arr_2[12:1];
initial
begin
for (int i = 3; i<=8 ; i++)
begin
fxd_arr_1[i] = $random() ;
$display("SV:fxd_arr_1 %0d %d ",i, fxd_arr_1[i] );
end
CODE: C_file.c
#include <stdio.h>
#include <svdpi.h>
void pass_array(const svOpenArrayHandle dyn_arr ) {
int i;
}
RESULTS:
SV:fxd_arr_1 3 303379748
SV:fxd_arr_1 4 -1064739199
SV:fxd_arr_1 5 -2071669239
SV:fxd_arr_1 6 -1309649309
SV:fxd_arr_1 7 112818957
SV:fxd_arr_1 8 1189058957
Passing fxd_arr_1 to C
C: 3 303379748
C: 4 -1064739199
C: 5 -2071669239
C: 6 -1309649309
C: 7 112818957
C: 8 1189058957
Passing fxd_arr_2 to C
C: 1 -1295874971
C: 2 -1992863214
C: 3 15983361
C: 4 114806029
C: 5 992211318
C: 6 512609597
C: 7 1993627629
C: 8 1177417612
C: 9 2097015289
C: 10 -482925370
C: 11 -487095099
C: 12 -720121174
Packed Arrays
A packed array is represented as an array of one or more elements (of type svBitVecVal for 2-
state values and svLogicVecVal for 4-state values), each element representing a group of 32 bits.
CODE:SV_file.sv
program main;
initial begin
get_nums(nums);
foreach (nums[i]) $display(i,nums[i]);
end
endprogram
CODE:C_file.c
#include "svdpi.h"
RESULTS:
00
11
22
33
44
55
66
77
88
99
svLow() shall return the minimum of left index and right index of the dimension.
svHigh() shall return the maximum of left index and right index of the dimension.
svIncrement() shall return 1 if left index is greater than or equal to right index and -1 if left
index is less than right index.
svLength() shall return the number of elements in the dimension, which is equivalent to high
index - low index + 1.
CODE: SV_file.sv
program main;
int fxd_arr_1[8:3];
int fxd_arr_2[1:13];
import "DPI-C" context function void pass_array(input int dyn_arr[] );
initial
begin
$display("\n Passing fxd_arr_1 to C \n");
pass_array( fxd_arr_1 );
$display("\n Passing fxd_arr_2 to C \n");
pass_array( fxd_arr_2 );
end
endprogram
CODE: C_file.c
#include <stdio.h>
#include <svdpi.h>
void pass_array(const svOpenArrayHandle dyn_arr ) {
printf("Array Pointer is %x \n", svGetArrayPtr(dyn_arr) );
printf(" Lower index %d \n", svLow(dyn_arr,1));
printf(" Higher index %d \n", svHigh(dyn_arr, 1) );
printf(" Left index %d \n", svLeft(dyn_arr,1), svRight(dyn_arr, 1) );
printf(" Right index %d \n", svRight(dyn_arr, 1) );
printf(" Length of array %d \n", svLength(dyn_arr,1) );
printf(" Incremental %d \n",svIncrement(dyn_arr,1));
printf("Dimentions of Array %d \n", svDimensions(dyn_arr ));
printf("Size of Array in bytes %d \n", svSizeOfArray(dyn_arr) );
}
RESULTS:
Passing fxd_arr_1 to C
Array Pointer is 80fdc58
Lower index 3
Higher index 8
Left index 8
Right index 3
Length of array 6
Incremental 1
Dimentions of Array 1
Size of Array in bytes 24
Passing fxd_arr_2 to C
In the following example, a "struct" is passed from SystemVerilog to C and also from C to
Systemverilog using import and export functions. While passing the "struct" data type, the data
is packed in to array and passed from SV to C and then the array is decoded back to Struct in C.
The same when the Struct is passed from C to SystemVerilog.
CODE: C_file.c
#include "stdio.h"
#include "vc_hdrs.h"
#include "svdpi.h"
extern "C" {
typedef struct{
int a;
int b;
char c;
} C_struct;
extern void export_func(svBitVec32 x[3] );
void import_func()
{
C_struct s_data;
unsigned int arr[3];
s_data.a = 51;
s_data.b = 242;
s_data.c = 35;
printf( "C : s_data.a = %d\n", s_data.a );
printf( "C : s_data.b = %d\n", s_data.b );
printf( "C : s_data.c = %d\n\n", s_data.c );
arr[0] = s_data.a ;
arr[1] = s_data.b ;
arr[2] = s_data.c ;
export_func(arr);
}
}
CODE: SV_file.sv
program main;
export "DPI-C" function export_func;
import "DPI-C" function void import_func();
SV_struct s_data;
s_data.a = arr[0];
s_data.b = arr[1]; s_data.c = arr[2];
$display("SV: s_data.a = %0d", s_data.a );
$display("SV: s_data.b = %0d", s_data.b );
$display("SV: s_data.c = %0d \n", s_data.c );
endfunction
initial
begin
import_func();
end
endprogram
RESULTS:
C : s_data.a = 51
C : s_data.b = 242
C : s_data.c = 35
SV: s_data.a = 51
SV: s_data.b = 242
SV: s_data.c = 35
Passing Openarray Structs
CODE: C_file.c
#include "svdpi.h"
C : 0 : [303379748,-1064739199]
C : 1 : [-2071669239,-1309649309]
C : 2 : [112818957,1189058957]
C : 3 : [-1295874971,-1992863214]
C : 4 : [15983361,114806029]
Passing Union Example
CODE:SV_file
module m;
typedef bit [2:0] A;
printf(" C: %s",str);
return 0;
}
RESULTS
C: HELLO: This string is created in SystemVerilog
Example: Passing String From C To Sv
From the Data type mapping table, a SystemVerilog "String" is mapped to "const char*" in C. In
the Following example, string "HELLO: This string is created in C" is assigned to a string and
passed as return value to function import "string_c2sv" and this import function is called in
SystemVerilog.
CODE: SV_file.v
program main;
string str;
import "DPI-C" context function string string_c2sv();
initial
begin
str = string_c2sv();
$display(" SV: %s ",str);
end
endprogram
CODE: C_file.c
#include "svdpi.h"
const char* string_c2sv(void) {
char* str;
str = " HELLO: This string is created in C ";
return str;
}
RESULTS:
SV: HELLO: This string is created in C
DISABLIE
Disable Dpi-C Tasks And Functions
It is possible for a disable statement to disable a block that is currently executing a mixed
language call chain. When a DPI import task or function is disabled, the C code is required to
follow a simple disable protocol. The protocol gives the C code the opportunity to perform any
necessary resource cleanup, such as closing open file handles, closing open VPI handles, or
freeing heap memory.
UVM TESTBENCH
Uvm components, uvm env and uvm test are the three main building blocks of a testbench in
uvm based verification.
Uvm_env
uvm_env is extended from uvm_component and does not contain any extra functionality.
uvm_env is used to create and connect the uvm_components like driver, monitors , sequeners
etc. A environment class can also be used as sub-environment in another environment. As there
is no difference between uvm_env and uvm_component , we will discuss about uvm_component,
in the next section.
Verification Components
uvm verification component classes are derived from uvm_component class which provides
features like hierarchy searching, phasing, configuration , reporting , factory and transaction
recording.
Following are some of the uvm component classes
uvm_agent
uvm_monitor
uvm_scoreboard
uvm_driver
uvm_sequencer
NOTE: uvm_env and uvm_test are also extended from uvm_component.
A typical uvm verification environment:
An agent typically contains three subcomponents: a driver, sequencer, and monitor. If the agent
is active, subtypes should contain all three subcomponents. If the agent is passive, subtypes
should contain only the monitor.
About Uvm_component Class:
uvm_compoenent class is inherited from uvm_report_object which is inherited from
uvm_object.
As I mentioned previously, uvm_component class provides features like hierarchy searching,
phasing, configuration , reporting , factory and transaction recording.
We will discuss about phasing concept in this section and rest of the features will be discussed as
separate topics.
UVM phases
UVM Components execute their behavior in strictly ordered, pre-defined phases. Each phase is
defined by its own virtual method, which derived components can override to incorporate
component-specific behavior. By default , these methods do nothing.
--> virtual function void build()
This phase is used to construct various child components/ports/exports and configures them.
In this phase , Main body of the test is executed where all threads are forked off.
In this phase, check the results of the extracted information such as un responded requests in
scoreboard, read statistics registers etc.
Only build() method is executed in top down manner. i.e after executing parent build() method,
child objects build() methods are executed. All other methods are executed in bottom-up
manner. The run() method is the only method which is time consuming. The run() method is
forked, so the order in which all components run() method are executed is undefined.
Uvm_test
uvm_test is derived from uvm_component class and there is no extra functionality is added. The
advantage of used uvm_test for defining the user defined test is that the test case selection can be
done from command line option +UVM_TESTNAME=<testcase_string> . User can also select
the testcase by passing the testcase name as string to
uvm_root::run_test(<testcase_string>) method.
`uvm_component_utils(env)
3)Declare the objects for agents.
agent ag1;
agent ag2;
4)Define the constructor. In the constructor, call the super methods and pass the parent object.
Parent is the object in which environment is instantiated.
6)Define connect(),end_of_elaboration(),start_of_simulation(),run(),extract(),check(),report()
methods.
Just print a message from these methods, as we dont have any logic in this example to define.
`uvm_component_utils(env)
agent ag1;
agent ag2;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build();
uvm_report_info(get_full_name(),"Build", UVM_LOW);
ag1 = agent::type_id::create("ag1",this);
ag2 = agent::type_id::create("ag2",this);
endfunction
function void connect();
uvm_report_info(get_full_name(),"Connect", UVM_LOW);
endfunction
function void end_of_elaboration();
uvm_report_info(get_full_name(),"End_of_elaboration", UVM_LOW);
endfunction
function void start_of_simulation();
uvm_report_info(get_full_name(),"Start_of_simulation", UVM_LOW);
endfunction
task run();
uvm_report_info(get_full_name(),"Run", UVM_LOW);
endtask
function void extract();
uvm_report_info(get_full_name(),"Extract", UVM_LOW);
endfunction
function void check();
uvm_report_info(get_full_name(),"Check", UVM_LOW);
endfunction
function void report();
uvm_report_info(get_full_name(),"Report", UVM_LOW);
endfunction
endclass
Now we will implement the testcase.
1)Extend uvm_test and define the test case
class test1 extends uvm_test;
2)Declare component ustilits using utility macro.
`uvm_component_utils(test1)
2)Declare environment class handle.
env t_env;
3)Define constructor method. In the constructor, call the super method and construct the
environment object.
function new (string name="test1", uvm_component parent=null);
super.new (name, parent);
t_env = new("t_env",this);
endfunction : new
4)Define the end_of_elaboration method. In this method, call the print() method. This print()
method will print the topology of the test.
function void end_of_elaboration();
uvm_report_info(get_full_name(),"End_of_elaboration", UVM_LOW);
print();
endfunction
4)Define the run method and call the global_stop_request() method.
task run ();
#1000;
global_stop_request();
endtask : run
Testcase source code:
class test1 extends uvm_test;
`uvm_component_utils(test1)
env t_env;
function new (string name="test1", uvm_component parent=null);
super.new (name, parent);
t_env = new("t_env",this);
endfunction : new
function void end_of_elaboration();
uvm_report_info(get_full_name(),"End_of_elaboration", UVM_LOW);
print();
endfunction
task run ();
#1000;
global_stop_request();
endtask : run
endclass
Top Module:
To start the testbench, run_test() method must be called from initial block.
Run_test() mthod Phases all components through all registered phases.
module top;
initial
run_test();
endmodule
Download the source code
uvm_phases.tar
Browse the code in uvm_phases.tar
uvm_test_top.t_env.ag1.drv[uvm_test_top.t_env.ag1.drv]Start_of_simulation
uvm_test_top.t_env.ag1.mon[uvm_test_top.t_env.ag1.mon]Start_of_simulation
uvm_test_top.t_env.ag1[uvm_test_top.t_env.ag1]Start_of_simulation
uvm_test_top.t_env.ag2.drv[uvm_test_top.t_env.ag2.drv]Start_of_simulation
uvm_test_top.t_env.ag2.mon[uvm_test_top.t_env.ag2.mon]Start_of_simulatio
..
..
..
..
Observe the above log report:
1)Build method was called in top-down fashion. Look at the following part of message.
uvm_test_top.t_env [uvm_test_top.t_env] Build
uvm_test_top.t_env.ag1 [uvm_test_top.t_env.ag1] Build
uvm_test_top.t_env.ag1.drv [uvm_test_top.t_env.ag1.drv] Build
uvm_test_top.t_env.ag1.mon [uvm_test_top.t_env.ag1.mon] Build
uvm_test_top.t_env.ag2 [uvm_test_top.t_env.ag2] Build
uvm_test_top.t_env.ag2.drv [uvm_test_top.t_env.ag2.drv] Build
uvm_test_top.t_env.ag2.mon [uvm_test_top.t_env.ag2.mon] Build
2)Connect method was called in bottopm up fashion. Look at the below part of log file,
uvm_test_top.t_env.ag1.drv [uvm_test_top.t_env.ag1.drv] Connect
uvm_test_top.t_env.ag1.mon [uvm_test_top.t_env.ag1.mon] Connect
uvm_test_top.t_env.ag1 [uvm_test_top.t_env.ag1] Connect
uvm_test_top.t_env.ag2.drv [uvm_test_top.t_env.ag2.drv] Connect
uvm_test_top.t_env.ag2.mon [uvm_test_top.t_env.ag2.mon] Connect
uvm_test_top.t_env.ag2 [uvm_test_top.t_env.ag2] Connect
uvm_test_top.t_env [uvm_test_top.t_env] Connect
3)Following part of log file shows the testcase topology.
UVM REPORTING
The uvm_report_object provides an interface to the UVM reporting facility. Through this
interface, components issue the various messages with different severity levels that occur during
simulation. Users can configure what actions are taken and what file(s) are output for individual
messages from a particular component or for all messages from all components in the
environment.
A report consists of an id string, severity, verbosity level, and the textual message itself. If the
verbosity level of a report is greater than the configured maximum verbosity level of its report
object, it is ignored.
Reporting Methods:
Following are the primary reporting methods in the UVM.
filename/line -- If required to print filename and line number from where the message is
issued, use macros, `__FILE__ and `__LINE__.
Actions:
These methods associate the specified action or actions with reports of the given severity, id, or
severity-id pair.
Following are the actions defined:
UVM_NO_ACTION -- Do nothing
UVM_DISPLAY -- Display report to standard output
UVM_LOG -- Write to a file
UVM_COUNT -- Count up to a max_quit_count value before exiting
UVM_EXIT -- Terminates simulation immediately
UVM_CALL_HOOK -- Callback the hook method .
Configuration:
Using these methods, user can set the verbosity levels and set actions.
`include "uvm.svh"
import uvm_pkg::*;
class rpting extends uvm_component;
`uvm_component_utils(rpting)
function new(string name,uvm_component parent);
super.new(name, parent);
endfunction
task run();
uvm_report_info(get_full_name(),
"Info Message : Verbo lvl - UVM_NONE ",UVM_NONE,`__FILE__,`__LINE__);
uvm_report_info(get_full_name(),
"Info Message : Verbo lvl - UVM_LOW ",UVM_LOW);
uvm_report_info(get_full_name(),
"Info Message : Verbo lvl - 150 ",150);
uvm_report_info(get_full_name(),
"Info Message : Verbo lvl - UVM_MEDIUM",UVM_MEDIUM);
uvm_report_warning(get_full_name(),
"Warning Messgae from rpting",UVM_LOW);
uvm_report_error(get_full_name(),
"Error Message from rpting \n\n",UVM_LOW);
endtask
endclass
module top;
rpting rpt1;
rpting rpt2;
rpting rpt3;
initial begin
rpt1 = new("rpt1",null);
rpt2 = new("rpt2",null);
rpt3 = new("rpt3",null);
rpt1.set_report_verbosity_level(UVM_MEDIUM);
rpt2.set_report_verbosity_level(UVM_LOW);
rpt3.set_report_verbosity_level(UVM_NONE);
run_test();
end
endmodule
Download the source code
uvm_reporting.tar
Browse the code in uvm_reporting.tar
FIG: UVM
OBJECT UTILITIES
User Defined Implementations:
User should define these methods in the transaction using do_<method_name> and call them
using <method_name>. Following table shows calling methods and user-defined hook
do_<method_name> methods. Clone and create methods, does not use hook methods concepts.
Shorthand Macros:
Using the field automation concept of uvm, all the above defines methods can be defined
automatically.
To use these field automation macros, first declare all the data fields, then place the field
automation macros between the `uvm_object_utils_begin and `uvm_object_utils_end macros.
Example of field automation macros:
class Packet extends uvm_transaction;
rand bit [7:0] da;
rand bit [7:0] sa;
rand bit [7:0] length;
rand bit [7:0] data[];
rand byte fcs;
`uvm_object_utils_begin(Packet)
`uvm_field_int(da, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(sa, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(length, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_array_int(data, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(fcs, UVM_ALL_ON|UVM_NOPACK)
`uvm_object_utils_end
endclass.
For most of the data types in systemverilog, uvm defined corresponding field automation
macros. Following table shows all the field automation macros.
Each `uvm_field_* macro has at least two arguments: ARG and FLAG.
ARG is the instance name of the variable and FLAG is used to control the field usage in core
utilities operation.
By default, FLAG is set to UVM_ALL_ON. All these flags can be ored. Using NO_* flags, can
turn of particular field usage in a paerticuler method. NO_* flags takes precedency over other
flags.
Example of Flags:
`uvm_field_int(da, UVM_ALL_ON|UVM_NOPACK)
The above macro will use the field "da" in all utilities methods except Packing and unpacking
methods.
Lets see a example:
In the following example, all the utility methods are defined using field automation macros
except Packing and unpacking methods. Packing and unpacking methods are done in do_pack()
amd do_unpack() method.
`include "uvm.svh"
import uvm_pkg::*;
//Define the enumerated types for packet types
typedef enum { GOOD_FCS, BAD_FCS } fcs_kind_t;
class Packet extends uvm_transaction;
rand fcs_kind_t fcs_kind;
rand bit [7:0] length;
rand bit [7:0] da;
rand bit [7:0] sa;
rand bit [7:0] data[];
rand byte fcs;
constraint payload_size_c { data.size inside { [1 : 6]};}
constraint length_c { length == data.size; }
function new(string name = "");
super.new(name);
endfunction : new
function void post_randomize();
if(fcs_kind == GOOD_FCS)
fcs = 8'b0;
else
fcs = 8'b1;
fcs = cal_fcs();
endfunction : post_randomize
///// method to calculate the fcs /////
virtual function byte cal_fcs;
integer i;
byte result ;
result = 0;
result = result ^ da;
result = result ^ sa;
result = result ^ length;
for (i = 0;i< data.size;i++)
result = result ^ data[i];
result = fcs ^ result;
return result;
endfunction : cal_fcs
`uvm_object_utils_begin(Packet)
`uvm_field_int(da, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(sa, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(length, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_array_int(data, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(fcs, UVM_ALL_ON|UVM_NOPACK)
`uvm_object_utils_end
function void do_pack(uvm_packer packer);
super.do_pack(packer);
packer.pack_field_int(da,$bits(da));
packer.pack_field_int(sa,$bits(sa));
packer.pack_field_int(length,$bits(length));
foreach(data[i])
packer.pack_field_int(data[i],8);
packer.pack_field_int(fcs,$bits(fcs));
endfunction : do_pack
function void do_unpack(uvm_packer packer);
int sz;
super.do_pack(packer);
da = packer.unpack_field_int($bits(da));
sa = packer.unpack_field_int($bits(sa));
length = packer.unpack_field_int($bits(length));
data.delete();
data = new[length];
foreach(data[i])
data[i] = packer.unpack_field_int(8);
fcs = packer.unpack_field_int($bits(fcs));
endfunction : do_unpack
endclass : Packet
/////////////////////////////////////////////////////////
//// Test to check the packet implementation ////
/////////////////////////////////////////////////////////
module test;
Packet pkt1 = new("pkt1");
Packet pkt2 = new("pkt2");
byte unsigned pkdbytes[];
initial
repeat(10)
if(pkt1.randomize)
begin
$display(" Randomization Sucessesfull.");
pkt1.print();
uvm_default_packer.use_metadata = 1;
void'(pkt1.pack_bytes(pkdbytes));
$display("Size of pkd bits %d",pkdbytes.size());
pkt2.unpack_bytes(pkdbytes);
pkt2.print();
if(pkt2.compare(pkt1))
$display(" Packing,Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare *** \n \n");
end
else
$display(" *** Randomization Failed ***");
endmodule
Download the source code
uvm_transaction.tar
Browse the code in uvm_transaction.tar
Command to run the simulation
VCS Users : make vcs
Questa Users: make questa
Log report:
Randomization Sucessesfull.
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt1 Packet - pkt1@3
da integral 8 'h1d
sa integral 8 'h26
length integral 8 'h5
data da(integral) 5 -
[0] integral 8 'hb1
[1] integral 8 'h3f
[2] integral 8 'h9e
[3] integral 8 'h38
[4] integral 8 'h8d
fcs integral 8 'h9b
----------------------------------------------------------------------
Size of pkd bits 9
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt2 Packet - pkt2@5
da integral 8 'h1d
sa integral 8 'h26
length integral 8 'h5
data da(integral) 5 -
[0] integral 8 'hb1
[1] integral 8 'h3f
[2] integral 8 'h9e
[3] integral 8 'h38
[4] integral 8 'h8d
fcs integral 8 'h9b
----------------------------------------------------------------------
Packing,Unpacking and compare worked
UVM CONFIGURATION
Configuration is a mechanism in UVM that higher level components in a hierarchy can
configure the lower level components variables. Using set_config_* methods, user can configure
integer, string and objects of lower level components. Without this mechanism, user should
access the lower level component using hierarchy paths, which restricts reusability. This
mechanism can be used only with components. Sequences and transactions cannot be configured
using this mechanism. When set_config_* method is called, the data is stored w.r.t strings in a
table. There is also a global configuration table.
Higher level component can set the configuration data in level component table. It is the
responsibility of the lower level component to get the data from the component table and update
the appropriate table.
Set_config_* Methods:
Following are the method to configure integer , string and object of uvm_object based class
respectively.
function void set_config_int (string inst_name,
string field_name,
uvm_bitstream_t value)
function void set_config_string (string inst_name,
string field_name,
string value)
function void set_config_object (string inst_name,
string field_name,
uvm_object value, bit clone = 1)
Arguments description:
"*abc" -All the lower level components which ends with "abc".
Example: "xabc","xyabc","xyzabc" ....
"abc*" -All the lower level components which starts with "abc".
Example: "abcx","abcxy","abcxyz" ....
"ab?" -All the lower level components which start with "ab" , then followed by one more
character.
Example: "abc","abb","abx" ....
"?bc" -All the lower level components which start with any one character ,then followed by
"c".
Example: "abc","xbc","bbc" ....
"a?c" -All the lower level components which start with "a" , then followed by one more
character and then followed by "c".
Example: "abc","aac","axc" …..
There are two ways to get the configuration data:
1)Automatic : Using Field macros
2)Manual : using gte_config_* methods.
Automatic Configuration:
To use the atomic configuration, all the configurable fields should be defined using uvm
component field macros and uvm component utilities macros.
uvm component utility macros:
For non parameterized classes
`uvm_component_utils_begin(TYPE)
`uvm_field_* macro invocations here
`uvm_component_utils_end
For parameterized classes.
`uvm_component_param_utils_begin(TYPE)
`uvm_field_* macro invocations her
`uvm_component_utils_end
For UVM Field macros, Refer to link
UVM_TRANSACTION
Example:
Following example is from link
UVM_TESTBENCH
2 Configurable fields, a integer and a string are defined in env, agent, monitor and driver classes.
Topology of the environment using these classes is
`uvm_component_utils(driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build();
super.build();
void'(get_config_int("int_cfg",int_cfg));
void'(get_config_string("str_cfg",str_cfg));
uvm_report_info(get_full_name(),
$psprintf("int_cfg %0d : str_cfg %0s ",int_cfg,str_cfg),UVM_LOW);
endfunction
endclass
Download the source code
uvm_configuration_2.tar
Browse the code in uvm_configuration_2.tar
Command to run the simulation
VCS Users : make vcs
Questa Users: make questa
Log file
UVM_INFO @ 0: uvm_test_top.t_env
int_cfg x : str_cfg abcd
UVM_INFO @ 0: uvm_test_top.t_env.ag1
int_cfg x : str_cfg
UVM_INFO @ 0: uvm_test_top.t_env.ag1.drv
int_cfg 32 : str_cfg pars
UVM_INFO @ 0: uvm_test_top.t_env.ag1.mon
int_cfg 32 : str_cfg pars
UVM_INFO @ 0: uvm_test_top.t_env.ag2
int_cfg x : str_cfg
UVM_INFO @ 0: uvm_test_top.t_env.ag2.drv
int_cfg 32 : str_cfg pars
UVM_INFO @ 0: uvm_test_top.t_env.ag2.mon
int_cfg 32 : str_cfg pars
Configuration Setting Members:
print_config_settings
function void print_config_settings
( string field = "",
uvm_component comp = null,
bit recurse = 0 )
This method prints all configuration information for this component.
If "field" is specified and non-empty, then only configuration settings matching that field, if any,
are printed. The field may not contain wildcards. If "recurse" is set, then information for all
children components are printed recursively.
print_config_matches
static bit print_config_matches = 0
Setting this static variable causes get_config_* to print info about matching configuration
settings as they are being applied. These two members will be helpful to know while debugging.
Download the source code
uvm_configuration_3.tar
Browse the code in uvm_configuration_3.tar
Command to run the simulation
VCS Users : make vcs
Questa Users: make questa
Log file
When print_config_settings method is called
uvm_test_top.t_env.ag1.drv
uvm_test_top.*.ag1.* int_cfg int 32
uvm_test_top.t_env.ag1.drv.rsp_port
uvm_test_top.*.ag?.* str_cfg string pars
uvm_test_top.t_env.ag1.drv.rsp_port
uvm_test_top.*.ag1.* int_cfg int 32
uvm_test_top.t_env.ag1.drv.sqr_pull_port
uvm_test_top.*.ag?.* str_cfg string pars
uvm_test_top.t_env.ag1.drv.sqr_pull_port
uvm_test_top.*.ag1.* int_cfg int 32
When print_config_matches is set to 1.
UVM_INFO @ 0: uvm_test_top.t_env [auto-configuration]
Auto-configuration matches for component uvm_test_top.t_env (env).
Last entry for a given field takes precedence.
1) Registration
2) Construction
3) Overriding
The factory makes it is possible to override the type of uvm component /object or instance of a
uvm component/object in2 ways. They are based on uvm component/object type or uvm
compoenent/object name.
Registration:
While defining a class , its type has to be registered with the uvm factory. To do this job easier,
uvm has predefined macros.
`uvm_component_utils(class_type_name)
`uvm_component_param_utils(class_type_name #(params))
`uvm_object_utils(class_type_name)
`uvm_object_param_utils(class_type_name #(params))
For uvm_*_param_utils are used for parameterized classes and other two macros for non-
parameterized class. Registration is required for name-based overriding , it is not required for
type-based overriding.
EXAMPLE: Example of above macros
class packet extends uvm_object;
`uvm_object_utils(packet)
endclass
class packet #(type T=int, int mode=0) extends uvm_object;
`uvm_object_param_utils(packet #(T,mode))
endclass
class driver extends uvm_component;
`uvm_component_utils(driver)
endclass
In this example, there is one driver class and one monitor class. In this testcase , By extending
driver class , we will define driver_2 class and by extending monitor class, we will define
monitor_2 class.
From the testcase , Using set_type_override_by_type, we will override driver with driver_2 and
Using set_type_override_by_name, we will override monitor with monitor_2.
To know about the overrides which are done, call factory.print() method of factory class.
class driver_2 extends driver;
`uvm_component_utils(driver_2)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
class monitor_2 extends monitor;
`uvm_component_utils(monitor_2)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
class test_factory extends uvm_test;
`uvm_component_utils(test_factory)
env t_env;
function new (string name="test1", uvm_component parent=null);
super.new (name, parent);
factory.set_type_override_by_type(driver::get_type(),driver_2::get_type(),"*");
factory.set_type_override_by_name("monitor","monitor_2","*");
factory.print();
t_env = new("t_env",this);
endfunction : new
function void end_of_elaboration();
uvm_report_info(get_full_name(),"End_of_elaboration", UVM_LOW);
print();
endfunction : end_of_elaboration
task run ();
#1000;
global_stop_request();
endtask : run
endclass
Download the example:
uvm_factory.tar
Browse the code in uvm_factory.tar
Command to simulate
Command to run the example with the testcase which is defined above:
VCS Users : make vcs
Questa Users: make questa
Method factory.print() displayed all the overrides as shown below in the log file.
Type Overrides:
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
uvm_test_top test_factory - uvm_test_top@2
t_env env - t_env@4
ag1 agent - ag1@6
drv driver_2 - drv@12
rsp_port uvm_analysis_port - rsp_port@16
sqr_pull_port uvm_seq_item_pull_+ - sqr_pull_port@14
mon monitor_2 - mon@10
ag2 agent - ag2@8
drv driver_2 - drv@20
rsp_port uvm_analysis_port - rsp_port@24
sqr_pull_port uvm_seq_item_pull_+ - sqr_pull_port@22
mon monitor_2 - mon@18
----------------------------------------------------------------------
In the below text printed by print_topology() method ,with testcase test1 which does note have
overrides.
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
uvm_test_top test1 - uvm_test_top@2
t_env env - t_env@4
ag1 agent - ag1@6
drv driver - drv@12
rsp_port uvm_analysis_port - rsp_port@16
sqr_pull_port uvm_seq_item_pull_+ - sqr_pull_port@14
mon monitor - mon@10
ag2 agent - ag2@8
drv driver - drv@20
rsp_port uvm_analysis_port - rsp_port@24
sqr_pull_port uvm_seq_item_pull_+ - sqr_pull_port@22
mon monitor - mon@18
----------------------------------------------------------------------
UVM SEQUENCE 1
Introduction
A sequence is a series of transaction. User can define the complex stimulus. sequences can be
reused, extended, randomized, and combined sequentially and hierarchically in various ways.
For example, for a processor, lets say PUSH_A,PUSH_B,ADD,SUB,MUL,DIV and POP_C are
the instructions. If the instructions are generated randomly, then to excursing a meaningful
operation like "adding 2 variables" which requires a series of transaction
"PUSH_A PUSH_B ADD POP_C " will take longer time. By defining these series of
"PUSH_A PUSH_B ADD POP_C ", it would be easy to exercise the DUT.
uvm_sequence_item :
User has to define a transaction by extending uvm_sequence_item. uvm_sequence_item class
provides the basic functionality for objects, both sequence items and sequences, to operate in the
sequence mechanism. For more information about uvm_sequence_item Refer to link
UVM_TRANSACTION
uvm_sequence:
User should extend uvm_sequence class and define the construction of sequence of transactions.
These transactions can be directed, constrained randomized or fully randomized. The
uvm_sequence class provides the interfaces necessary in order to create streams of sequence
items and/or other sequences.
virtual class uvm_sequence #(
type REQ = uvm_sequence_item,
type RSP = REQ
)
uvm_sequencer:
uvm_sequencer is responsible for the coordination between sequence and driver. Sequencer
sends the transaction to driver and gets the response from the driver. The response transaction
from the driver is optional. When multiple sequences are running in parallel, then sequencer is
responsible for arbitrating between the parallel sequences. There are two types of sequencers :
uvm_sequencer and uvm_push_sequencer
class uvm_sequencer #(
type REQ = uvm_sequence_item,
type RSP = REQ
)
class uvm_push_sequencer #(
type REQ = uvm_sequence_item,
type RSP = REQ
)
uvm driver:
User should extend uvm_driver class to define driver component. uvm driver is a component that
initiate requests for new transactions and drives it to lower level components. There are two
types of drivers: uvm_driver and uvm_push_driver.
class uvm_driver #(
type REQ = uvm_sequence_item,
type RSP = REQ
)
class uvm_push_driver #(
type REQ = uvm_sequence_item,
type RSP = REQ
)
The above image shows how a transaction from a sequence is sent to driver and the response
from the driver is sent to sequencer. There are multiple methods called during this operation.
3) In the run task of the driver, when "seq_item_port.get_next_item()" is called, then the
sequencer un blocks wait_for_grant() method. If more than one sequence is getting executed by
sequencer, then based on arbitration rules, un blocks the wait_for_grant() method.
4) After the wait_for_grant() un blocks, then transaction can be randomized, or its properties can
be filled directly. Then using the send_request() method, send the transaction to the driver.
After this step, again the steps 1 to 7 are repeated five times.
If a response from driver is not required, then steps 5,6,7 can be skipped and item_done() method
from driver should be called as shown in above image.
Simple Example
Lest write an example: This is a simple example of processor instruction. Various instructions
which are supported by the processor are PUSH_A,PUSH_B,ADD,SUB,MUL,DIV and POP_C.
Sequence Item
1) Extend uvm_sequence_item and define instruction class.
class instruction extends uvm_sequence_item;
2) Define the instruction as enumerated types and declare a variable of instruction enumerated
type.
typedef enum {PUSH_A,PUSH_B,ADD,SUB,MUL,DIV,POP_C} inst_t;
rand inst_t inst;
3) Define operational method using uvm_field_* macros.
`uvm_object_utils_begin(instruction)
`uvm_field_enum(inst_t,inst, UVM_ALL_ON)
`uvm_object_utils_end
4) Define the constructor.
function new (string name = "instruction");
super.new(name);
endfunction
Sequence item code:
class instruction extends uvm_sequence_item;
typedef enum {PUSH_A,PUSH_B,ADD,SUB,MUL,DIV,POP_C} inst_t;
rand inst_t inst;
`uvm_object_utils_begin(instruction)
`uvm_field_enum(inst_t,inst, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name = "instruction");
super.new(name);
endfunction
endclass
Sequence
We will define a operation addition using uvm_sequence. The instruction sequence should be
"PUSH A PUSH B ADD POP C".
1) Define a sequence by extending uvm_sequence. Set REQ parameter to "instruction" type.
class operation_addition extends uvm_sequence #(instruction);
2) Define the constructor.
function new(string name="operation_addition");
super.new(name);
endfunction
3) Lets name the sequencer which we will develop is "instruction_sequencer".
Using the `uvm_sequence_utils macro, register the "operation_addition" sequence with
"instruction_sequencer" sequencer. This macro adds the sequence to the sequencer list. This
macro will also register the sequence for factory overrides.
`uvm_sequence_utils(operation_addition, instruction_sequencer)
4)
In the body() method, first call wait_for_grant(), then construct a transaction and set the
instruction enum to PUSH_A . Then send the transaction to driver using send_request() method.
Then call the wait_for_item_done() method. Repeat the above steps for other instructions
PUSH_B, ADD and POP_C.
For construction of a transaction, we will use the create() method.
virtual task body();
req = instruction::type_id::create("req");
wait_for_grant();
assert(req.randomize() with {
inst == instruction::PUSH_A;
});
send_request(req);
wait_for_item_done();
//get_response(res); This is optional. Not using in this example.
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::PUSH_B;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::ADD;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::POP_C;
send_request(req);
wait_for_item_done();
//get_response(res);
endtask
Sequence code
class operation_addition extends uvm_sequence #(instruction);
instruction req;
function new(string name="operation_addition");
super.new(name);
endfunction
`uvm_sequence_utils(operation_addition, instruction_sequencer)
virtual task body();
req = instruction::type_id::create("req");
wait_for_grant();
assert(req.randomize() with {
inst == instruction::PUSH_A;
});
send_request(req);
wait_for_item_done();
//get_response(res); This is optional. Not using in this example.
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::PUSH_B;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::ADD;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::POP_C;
send_request(req);
wait_for_item_done();
//get_response(res);
endtask
endclass
Sequencer:
uvm_sequence has a property called default_sequence. Default sequence is a sequence which
will be started automatically. Using set_config_string, user can override the default sequence to
any user defined sequence, so that when a sequencer is started, automatically a user defined
sequence will be started. If over rides are not done with user defined sequence, then a random
transaction are generated. Using "start_default_sequence()" method, "default_sequence" can
also be started.
uvm sequencer has seq_item_export and res_export tlm ports for connecting to uvm driver.
`uvm_sequencer_utils(instruction_sequencer)
endclass
Driver:
uvm_driver is a class which is extended from uvm_componenet. This driver is used in pull
mode. Pull mode means, driver pulls the transaction from the sequencer when it requires.
uvm driver has 2 TLM ports.
1) Seq_item_port: To get a item from sequencer, driver uses this port. Driver can also send
response back using this port.
2) Rsp_port : This can also be used to send response back to sequencer.
Seq_item_port methods:
Lets implement a driver:
1) Define a driver which takes the instruction from the sequencer and does the processing. In this
example we will just print the instruction type and wait for some delay.
class instruction_driver extends uvm_driver #(instruction);
2) Place the uvm_component_utils macro to define virtual methods like get_type_name and
create.
`uvm_component_utils(instruction_driver)
3) Define Constructor method.
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction
4) Define the run() method. Run() method is executed in the "run phase". In this methods,
transactions are taken from the sequencer and drive them on to dut interface or to other
components.
Driver class has a port "seq_item_port". Using the method seq_item_port.get_next_item(), get
the transaction from the sequencer and process it. Once the processing is done, using the
item_done() method, indicate to the sequencer that the request is completed. In this example,
after taking the transaction, we will print the transaction and wait for 10 units time.
task run ();
while(1) begin
seq_item_port.get_next_item(req);
$display("%0d: Driving Instruction %s",$time,req.inst.name());
#10;
seq_item_port.item_done();
end
endtask
endclass
Driver class code:
class instruction_driver extends uvm_driver #(instruction);
Testcase:
This testcase is used only for the demo purpose of this tutorial session. Actually, the sequencer
and the driver and instantiated and their ports are connected in a agent component and used. Lets
implement a testcase
1) Take instances of sequencer and driver and construct both components.
sequencer = new("sequencer", null);
sequencer.build();
driver = new("driver", null);
driver.build();
2)
Connect the seq_item_export to the drivers seq_item_port.
driver.seq_item_port.connect(sequencer.seq_item_export);
3) Using set_confg_string() method, set the default sequence of the sequencer to
"operation_addition". Operation_addition is the sequence which we defined previous.
endmodule
Download the example:
uvm_basic_sequence.tar
Browse the code in uvm_basic_sequence.tar
Command to simulate
VCS Users : make vcs
Questa Users: make questa
Log file Output
UVM_INFO @ 0 [RNTST] Running test ...
0: Driving Instruction PUSH_A
10: Driving Instruction PUSH_B
20: Driving Instruction ADD
30: Driving Instruction POP_C
From the above log , we can see that transactions are generates as we defined in uvm sequence.
Pre Defined Sequences:
Every sequencer in uvm has 3 pre defined sequences. They are
1)uvm_random_sequence
2)uvm_exhaustive_sequence.
3)uvm_simple_sequence
All the user defined sequences which are registered by user and the above three predefined
sequences are stored in sequencer queue.
uvm_random_sequence :
This sequence randomly selects and executes a sequence from the sequencer sequence library,
excluding uvm_random_sequence itself, and uvm_exhaustive_sequence. From the above image,
from sequence id 2 to till the last sequence, all the sequences are executed randomly. If the
"count" variable of the sequencer is set to 0, then non of the sequence is executed. If the "count"
variable of the sequencer is set to -1, then some random number of sequences from 0 to
"max_random_count" are executed. By default "max_random_count" is set to 10. "Count" and
"max_random_count" can be changed using set_config_int().
The sequencer when automatically started executes the sequence which is point by
default_sequence. By default default_sequence variable points to uvm_random_sequence.
uvm_exhaustive_sequence:
This sequence randomly selects and executes each sequence from the sequencers sequence
library once in a randc style, excluding itself and uvm_random_sequence.
uvm_simple_sequence:
This sequence simply executes a single sequence item.
In the previous example from UVM_SEQUENCE_1 section.
The print() method of the sequencer in that example printed the following
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
sequencer instruction_sequen+ - sequencer@2
rsp_export uvm_analysis_export - rsp_export@4
seq_item_export uvm_seq_item_pull_+ - seq_item_export@28
default_sequence string 18 operation_addition
count integral 32 -1
max_random_count integral 32 'd10
sequences array 4 -
[0] string 19 uvm_random_sequence
[1] string 23 uvm_exhaustive_sequ+
[2] string 19 uvm_simple_sequence
[3] string 18 operation_addition
max_random_depth integral 32 'd4
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
----------------------------------------------------------------------
All these steps have be automated using "sequence action macros". There are some more
additional steps added in these macros. Following are the steps defined with the "sequence action
macro".
Pre_do(), mid_do() and post_do() are callback methods which are in uvm sequence. If user is
interested , he can use these methods. For example, in mid_do() method, user can print the
transaction or the randomized transaction can be fined tuned. These methods should not be
clled by user directly.
Syntax:
virtual task pre_do(bit is_item)
virtual function void mid_do(uvm_sequence_item this_item)
virtual function void post_do(uvm_sequence_item this_item)
Pre_do() is a task , if the method consumes simulation cycles, the behavior may be unexpected.
super.new(name);
endfunction
`uvm_sequence_utils(demo_uvm_do, instruction_sequencer)
virtual task pre_do(bit is_item);
uvm_report_info(get_full_name(),
"Seuqnce Action Macro Phase : PRE_DO ",UVM_LOW);
endtask
virtual function void mid_do(uvm_sequence_item this_item);
uvm_report_info(get_full_name(),
"Seuqnce Action Macro Phase : MID_DO ",UVM_LOW);
endfunction
virtual function void post_do(uvm_sequence_item this_item);
uvm_report_info(get_full_name(),
"Seuqnce Action Macro Phase : POST_DO ",UVM_LOW);
endfunction
virtual task body();
uvm_report_info(get_full_name(),
"Seuqnce Action Macro Phase : Before uvm_do macro ",UVM_LOW);
`uvm_do(req);
uvm_report_info(get_full_name(),
"Seuqnce Action Macro Phase : After uvm_do macro ",UVM_LOW);
endtask
endclass
Download the example
uvm_sequence_2.tar
Browse the code in uvm_sequence_2.tar
Command to run the simulation
VCS Users : make vcs
Questa Users: make questa
Log file report:
UVM_INFO@0:reporter[sequencer.demo_uvm_do]
Seuqnce Action Macro Phase : Before uvm_do macro
UVM_INFO@0:reporter[sequencer.demo_uvm_do]
Seuqnce Action Macro Phase : PRE_DO
UVM_INFO@0:reporter[sequencer.demo_uvm_do]
Seuqnce Action Macro Phase : MID_DO
0: Driving Instruction MUL
UVM_INFO@10:reporter[sequencer.demo_uvm_do]
Seuqnce Action Macro Phase : POST_DO
UVM_INFO@10:reporter[sequencer.demo_uvm_do]
Seuqnce Action Macro Phase : After uvm_do macro
The above log file shows the messages from pre_do,mid_do and post_do methods.
List Of Sequence Action Macros:
These macros are used to start sequences and sequence items that were either registered with a
<`uvm-sequence_utils> macro or whose associated sequencer was already set using the
<set_sequencer> method.
`uvm_create(item/sequence)
This action creates the item or sequence using the factory. Only the create phase will be
executed.
`uvm_do(item/sequence)
This macro takes as an argument a uvm_sequence_item variable or sequence . All the above
defined 7 phases will be executed.
`uvm_do_with(item/sequence, Constraint block)
This is the same as `uvm_do except that the constraint block in the 2nd argument is applied to
the item or sequence in a randomize with statement before execution.
`uvm_send(item/sequence)
Create phase and randomize phases are skipped, rest all the phases will be executed. Using
`uvm_create, create phase can be executed. Essentially, an `uvm_do without the create or
randomization.
`uvm_rand_send(item/sequence)
Only create phase is skipped. rest of all the phases will be executed. User should use
`uvm_create to create the sequence or item.
`uvm_rand_send_with(item/sequence , Constraint block)
Only create phase is skipped. rest of all the phases will be executed. User should use
`uvm_create to create the sequence or item. Constraint block will be applied which
randomization.
`uvm_do_pri(item/sequence, priority )
This is the same as `uvm_do except that the sequence item or sequence is executed with the
priority specified in the argument.
`uvm_do_pri_with(item/sequence , constraint block , priority)
This is the same as `uvm_do_pri except that the given constraint block is applied to the item or
sequence in a randomize with statement before execution.
`uvm_send_pri(item/sequence,priority)
This is the same as `uvm_send except that the sequence item or sequence is executed with the
priority specified in the argument.
`uvm_rand_send_pri(item/sequence,priority)
This is the same as `uvm_rand_send except that the sequence item or sequence is executed with
the priority specified in the argument.
`uvm_rand_send_pri_with(item/sequence,priority,constraint block)
This is the same as `uvm_rand_send_pri except that the given constraint block is applied to the
item or sequence in a randomize with statement before execution.
Following macros are used on sequence or sequence items on a different sequencer.
`uvm_create_on(item/sequence,sequencer)
This is the same as `uvm_create except that it also sets the parent sequence to the sequence in
which the macro is invoked, and it sets the sequencer to the specified sequencer argument.
`uvm_do_on(item/sequence,sequencer)
This is the same as `uvm_do except that it also sets the parent sequence to the sequence in which
the macro is invoked, and it sets the sequencer to the specified sequencer argument.
`uvm_do_on_pri(item/sequence,sequencer, priority)
This is the same as `uvm_do_pri except that it also sets the parent sequence to the sequence in
which the macro is invoked, and it sets the sequencer to the specified sequencer argument.
`uvm_do_on_with(item/sequence,sequencer, constraint block)
This is the same as `uvm_do_with except that it also sets the parent sequence to the sequence in
which the macro is invoked, and it sets the sequencer to the specified sequencer argument. The
user must supply brackets around the constraints.
`uvm_do_on_pri_with(item/sequence,sequencer,priority,constraint block)
This is the same as `uvm_do_pri_with except that it also sets the parent sequence to the sequence
in which the macro is invoked, and it sets the sequencer to the specified sequencer argument.
Examples With Sequence Action Macros:
virtual task body();
uvm_report_info(get_full_name(),
"Executing Sequence Action Macro uvm_do",UVM_LOW);
`uvm_do(req)
endtask
virtual task body();
uvm_report_info(get_full_name(),
"Executing Sequence Action Macro uvm_do_with ",UVM_LOW);
`uvm_do_with(req,{ inst == ADD; })
endtask
virtual task body();
uvm_report_info(get_full_name(),
"Executing Sequence Action Macro uvm_create and uvm_send",UVM_LOW);
`uvm_create(req)
req.inst = instruction::PUSH_B;
`uvm_send(req)
endtask
virtual task body();
uvm_report_info(get_full_name(),
"Executing Sequence Action Macro uvm_create and uvm_rand_send",UVM_LOW);
`uvm_create(req)
`uvm_rand_send(req)
endtask
uvm_sequence_3.tar
Browse the code in uvm_sequence_3.tar
Command to sun the simulation
VCS Users : make vcs
Questa Users: make questa
Log file report
0: Driving Instruction PUSH_B
UVM_INFO@10:reporter[***]Executing Sequence Action Macro uvm_do_with
10: Driving Instruction ADD
UVM_INFO@20:reporter[***]Executing Sequence Action Macro uvm_create and uvm_send
20: Driving Instruction PUSH_B
UVM_INFO@30:reporter[***]Executing Sequence Action Macro uvm_do
30: Driving Instruction DIV
UVM_INFO@40:reporter[***]Executing Sequence Action Macro uvm_create and
uvm_rand_send
40: Driving Instruction MUL
UVM SEQUENCE 3
Body Callbacks:
uvm sequences has two callback methods pre_body() and post_body(), which are executed
before and after the sequence body() method execution. These callbacks are called only when
start_sequence() of sequencer or start() method of the sequence is called. User should not call
these methods.
virtual task pre_body()
virtual task post_body()
Example
In this example, I just printed messages from pre_body() and post_body() methods. These
methods can be used for initialization, synchronization with some events or cleanup.
class demo_pre_body_post_body extends uvm_sequence #(instruction);
instruction req;
function new(string name="demo_pre_body_post_body");
super.new(name);
endfunction
`uvm_sequence_utils(demo_pre_body_post_body, instruction_sequencer)
virtual task pre_body();
uvm_report_info(get_full_name()," pre_body() callback ",UVM_LOW);
endtask
endclass
uvm_sequence_4.tar
Browse the code in uvm_sequence_4.tar
Command to sun the simulation
Parallelsequences
To executes child sequences Parallel, child sequence start() method should be called parallel
using fork/join in body method.
Parallel Sequence code:
class parallel_sequence extends uvm_sequence #(instruction);
seq_a s_a;
seq_b s_b;
function new(string name="parallel_sequence");
super.new(name);
endfunction
`uvm_sequence_utils(parallel_sequence, instruction_sequencer)
virtual task body();
fork
`uvm_do(s_a)
`uvm_do(s_b)
join
endtask
endclass
Download the example
uvm_sequence_6.tar
Browse the code in uvm_sequence_6.tar
Command to sun the simulation
VCS Users : make vcs
Questa Users: make questa
Log file report
UVM_INFO @ 0 [RNTST] Running test ...
0: Driving Instruction PUSH_A
10: Driving Instruction PUSH_B
20: Driving Instruction PUSH_A
30: Driving Instruction PUSH_B
40: Driving Instruction PUSH_A
50: Driving Instruction PUSH_B
60: Driving Instruction PUSH_A
70: Driving Instruction PUSH_B
UVM SEQUENCE 4
Sequencer Arbitration:
When sequences are executed parallel, sequencer will arbitrate among the parallel sequence.
When all the parallel sequences are waiting for a grant from sequencer using wait_for_grant()
method, then the sequencer, using the arbitration mechanism, sequencer grants to one of the
sequencer.
There are 6 different arbitration algorithms, they are
To set the arbitaration, use the set_arbitration() method of the sequencer. By default , the
arbitration algorithms is set to SEQ_ARB_FIFO.
UVM SEQUENCE 5
Sequencer Registration Macros
Sequence Registration Macros does the following
1) Implements get_type_name method.
2) Implements create() method.
3) Registers with the factory.
4) Implements the static get_type() method.
5) Implements the virtual get_object_type() method.
6) Registers the sequence type with the sequencer type.
7) Defines p_sequencer variable. p_sequencer is a handle to its sequencer.
8) Implements m_set_p_sequencer() method.
If there are no local variables, then use following macro
`uvm_sequence_utils(TYPE_NAME,SQR_TYPE_NAME)
If there are local variables in sequence, then use macro
`uvm_sequence_utils_begin(TYPE_NAME,SQR_TYPE_NAME)
`uvm_field_* macro invocations here
`uvm_sequence_utils_end
Macros `uvm_field_* are used for define utility methods.
These `uvm_field_* macros are discussed in
UVM_TRANSACTION
Example to demonstrate the usage of the above macros:
class seq_mul extends uvm_sequence #(instruction);
rand integer num_inst ;
instruction req;
constraint num_c { num_inst inside { 3,5,7 }; };
`uvm_sequence_utils_begin(seq_mul,instruction_sequencer)
`uvm_field_int(num_inst, UVM_ALL_ON)
`uvm_sequence_utils_end
driver.seq_item_port.connect(sequencer.seq_item_export);
sequencer.print();
fork
begin
run_test();
sequencer.start_default_sequence();
end
#3000 global_stop_request();
join
end
endmodule
Download the example
uvm_sequence_10.tar
Browse the code in uvm_sequence_10.tar
Command to sun the simulation
VCS Users : make vcs
Questa Users: make questa
Log
UVM_INFO @ 0: reporter [RNTST] Running test ...
UVM_INFO @ 0: reporter [sequencer.seq_mul] Num of transactions 3
0: Driving Instruction MUL
10: Driving Instruction MUL
20: Driving Instruction MUL
From the above log we can see that seq_mul.num_inst value is 3.
UVM SEQUENCE 6
Exclusive Access
A sequence may need exclusive access to the driver which sequencer is arbitrating among
multiple sequence. Some operations require that a series of transaction needs to be driven
without any other transaction in between them. Then a exclusive access to the driver will allow
to a sequence to complete its operation with out any other sequence operations in between them.
There are 2 mechanisms to get exclusive access:
Lock-unlcok
Grab-ungrab
Lock-Unlock
In the above environment, methods are used to transfer the data between components. So, this
gives a better control and data transfer is done at high level. The disadvantage is, components are
using hierarchal paths which do not allow the reusability.
TLM interfaces:
UVM has TLM interfaces which provide the advantages which we saw in the above two data
transfer styles.
Data is transferred at high level. Transactions which are developed by extending the
uvm_sequence_item can be transferred between components using method calls. These methods
are not hierarchal fixed, so that components can be reused.
The advantages of TLM interfaces are
1) Higher level abstraction
2) Reusable. Plug and play connections.
3) Maintainability
4) Less code.
5) Easy to implement.
6) Faster simulation.
7) Connect to Systemc.
8) Can be used for reference model development.
Operation Supported By Tlm Interface:
Putting:
Producer transfers a value to Consumer.
Getting:
Consumer requires a data value from producer.
Peeking:
Copies data from a producer without consuming the data.
Broadcasting:
Transaction is broadcasted to none or one or multiple consumers.
Methods
BLOCKING:
virtual task put(input T1 t)
virtual task get(output T2 t)
virtual task peek(output T2 t)
NON-BLOCKIN:
virtual function bit try_put(input T1 t)
virtual function bit can_put()
virtual function bit try_get(output T2 t)
virtual function bit can_get()
virtual function bit try_peek(output T2 t)
virtual function bit can_peek()
BLOCKING TRANSPORT:
virtual task transport(input T1 req,output T2 rsp)
NON-BLOCKING TRANSPORT:
virtual function bit nb_transport(input T1 req,output T2 rsp)
ANALYSIS:
virtual function void write(input T1 t)
Tlm Terminology :
Producer:
A component which generates a transaction.
Consumer:
A component which consumes the transaction.
Initiator:
A component which initiates process.
Target:
A component which responded to initiator.
Tlm Interface Compilation Models:
Blocking:
A blocking interface conveys transactions in blocking fashion; its methods do not return until the
transaction has been successfully sent or retrieved. Its methods are defined as tasks.
Non-blocking:
A non-blocking interface attempts to convey a transaction without consuming simulation
time. Its methods are declared as functions. Because delivery may fail (e.g. the target
component is busy and can not accept the request), the methods may return with failed status.
Combined:
A combination interface contains both the blocking and non-blocking variants.
Interfaces:
The UVM provides ports, exports and implementation and analysis ports for connecting your
components via the TLM interfaces. Port, Export, implementation terminology applies to control
flow not to data flow.
Port:
Interface that requires an implementation is port.
Import:
Interface that provides an implementation is import ot implementation port.
Export:
Interface used to route transaction interfaces to other layers of the hierarchy.
Analysis:
Interface used to distribute transactions to passive components.
Direction:
Unidirectional:
Data transfer is done in a single direction and flow of control is in either or both direction.
Bidirectional:
Data transfer is done in both directions and flow of control is in either or both directions.
Examples:
A read operation is a bidirectional.
A write operation is unidirectional.
`uvm_object_utils_begin(instruction)
`uvm_field_enum(inst_t,inst, UVM_ALL_ON)
`uvm_object_utils_end
uvm_tlm_2.tar
Browse the code in uvm_tlm_2.tar
Command to sun the simulation
VCS Users : make vcs
Questa Users: make questa
All Interfaces In Uvm:
uvm_blocking_put_port #(T)
uvm_nonblocking_put_port #(T)
uvm_put_port #(T)
uvm_blocking_get_port #(T)
uvm_nonblocking_get_port #(T)
uvm_get_port #(T)
uvm_blocking_peek_port #(T)
uvm_nonblocking_peek_port #(T)
uvm_peek_port #(T)
uvm_blocking_get_peek_port #(T)
uvm_nonblocking_get_peek_port #(T)
uvm_get_peek_port #(T)
uvm_analysis_port #(T)
uvm_transport_port #(REQ,RSP)
uvm_blocking_transport_port #(REQ,RSP)
uvm_nonblocking_transport_port #(REQ,RSP)
uvm_master_port #(REQ,RSP)
uvm_blocking_master_port #(REQ,RSP)
uvm_nonblocking_master_port #(REQ,RSP)
uvm_slave_port #(REQ,RSP)
uvm_blocking_slave_port #(REQ,RSP)
uvm_nonblocking_slave_port #(REQ,RSP)
uvm_put_export #(T)
uvm_blocking_put_export #(T)
uvm_nonblocking_put_export #(T)
uvm_get_export #(T)
uvm_blocking_get_export #(T)
uvm_nonblocking_get_export #(T)
uvm_peek_export #(T)
uvm_blocking_peek_export #(T)
uvm_nonblocking_peek_export #(T)
uvm_get_peek_export #(T)
uvm_blocking_get_peek_export #(T)
uvm_nonblocking_get_peek_export #(T)
uvm_analysis_export #(T)
uvm_transport_export #(REQ,RSP)
uvm_nonblocking_transport_export #(REQ,RSP)
uvm_master_export #(REQ,RSP)
uvm_blocking_master_export #(REQ,RSP)
uvm_nonblocking_master_export #(REQ,RSP)
uvm_slave_export #(REQ,RSP)
uvm_blocking_slave_export #(REQ,RSP)
uvm_nonblocking_slave_export #(REQ,RSP)
uvm_put_imp #(T,IMP)
uvm_blocking_put_imp #(T,IMP)
uvm_nonblocking_put_imp #(T,IMP)
uvm_get_imp #(T,IMP)
uvm_blocking_get_imp #(T,IMP)
uvm_nonblocking_get_imp #(T,IMP)
uvm_peek_imp #(T,IMP)
uvm_blocking_peek_imp #(T,IMP)
uvm_nonblocking_peek_imp #(T,IMP)
uvm_get_peek_imp #(T,IMP)
uvm_blocking_get_peek_imp #(T,IMP)
uvm_nonblocking_get_peek_imp #(T,IMP)
uvm_analysis_imp #(T,IMP)
uvm_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_blocking_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_nonblocking_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_master_imp #(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_blocking_master_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_nonblocking_master_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_slave_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_blocking_slave_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
uvm_nonblocking_slave_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP)
UVM TLM 2
Analysis
The analysis port is used to perform non-blocking broadcasts of transactions. It is by
components like monitors/drivers to publish transactions to its subscribers, which are typically
scoreboards and response/coverage collectors. For each port, more than one component can be
connected. Even if a component is not connected to the port, simulation can continue, unlike
put/get ports where simulation is not continued.
The uvm_analysis_port consists of a single function, write(). Subscriber component should
provide an implementation of write()method. UVM provides the uvm_subscriber base
component to simplify this operation, so a typical analysis component would extend
uvm_subscriber and its export is analysis_export.
Lets write a example.
In the example, we will define a monitor component and a subscriber.
Monitor source code:
In monitor, call the function write() pass the transaction.
class monitor extends uvm_monitor;
uvm_analysis_port #(instruction) anls_port;
function new(string name, uvm_component p = null);
super.new(name,p);
anls_port = new("anls_port", this);
endfunction
task run;
instruction inst;
inst = new();
#10ns;
inst.inst = instruction::MUL;
anls_port.write(inst);
#10ns;
inst.inst = instruction::ADD;
anls_port.write(inst);
#10ns;
inst.inst = instruction::SUB;
anls_port.write(inst);
endtask
endclass
Subscriber source code:
In Subscriber, define the write() method.
class subscriber extends uvm_subscriber#(instruction);
`uvm_register_cb(Driver,Driver_callback)
function new (string name, uvm_component parent=null);
super.new(name,parent);
endfunction
virtual task run();
repeat(2) begin
`uvm_do_callbacks(Driver,Driver_callback,pre_send())
$display(" Driver: Started Driving the packet ...... %d",$time);
// Logic to drive the packet goes hear
// let's consider that it takes 40 time units to drive a packet.
#40;
$display(" Driver: Finished Driving the packet ...... %d",$time);
`uvm_do_callbacks(Driver,Driver_callback,post_send())
end
endtask
endclass
Let's run the driver in simple testcase. In this testcase, we are not changing any callback methods
definitions.
Custom_Driver_callbacks_1 cb_1;
cb_1 = new("cb_1");
3) Register the callback method with the driver component. uvm_callback class has static
method add() which is used to register the callback.
uvm_callbacks #(Driver,Driver_callback)::add(drvr,cb_1);
Testcase 2 Source Code
class Custom_Driver_callbacks_1 extends Driver_callback;
The log results show that pre_send() method of CDc_1 is called first and then pre_send()
method of Cdc_2. This is because of the order of the registering callbacks.
uvm_callbacks #(Driver,Driver_callback)::add(drvr,cb_1);
uvm_callbacks #(Driver,Driver_callback)::add(drvr,cb_2);
Now we will see how to change the order of the callback method calls.
By changing the sequence of calls to add() method, order of callback method calling can be
changed.
Testcase 4 Source Code
module test;
initial begin
Driver drvr;
Custom_Driver_callbacks_1 cb_1;
Custom_Driver_callbacks_2 cb_2;
drvr = new("drvr");
cb_1 = new("cb_1");
cb_2 = new("cb_2");
uvm_callbacks #(Driver,Driver_callback)::add(drvr,cb_2);
uvm_callbacks #(Driver,Driver_callback)::add(drvr,cb_1);
uvm_callbacks #(Driver,Driver_callback)::display();
run_test();
end
endmodule
Download the source code
uvm_callback_4.tar
Browse the code in uvm_callback_4.tar
Command to run the simulation
VCS Users : make vcs
Questa Users: make questa
Run and analyze the results.
Log results show that, pre_send() method of CDs_1 is called after calling CDs_2 pre_send()
method.
Log file report
UVM_INFO @ 0: reporter [RNTST] Running test ...
CB_2:pre_send: Hai .... this is from Second callback 0
CB_1:pre_send: Delaying the packet driving by 20 time units. 0
Driver: Started Driving the packet ...... 20
Driver: Finished Driving the packet ...... 60
CB_1:post_send: Just a message from post send callback method
uvm also provides uvm_callbacks::delete() method to remove the callback methods which are
registered.
Similar to delete, delete_by_name() method is used to remove the callback using the object
name.
static function void delete_by_name(string name,
uvm_callback cb,
uvm_component root )
Macros:
`uvm_register_cb
Registers the given CB callback type with the given T object type.
`uvm_set_super_type
Defines the super type of T to be ST.
`uvm_do_callbacks
Calls the given METHOD of all callbacks of type CB registered with the calling object
`uvm_do_obj_callbacks
Calls the given METHOD of all callbacks based on type CB registered with the given object,
OBJ, which is or is based on type T.
`uvm_do_callbacks_exit_on
Calls the given METHOD of all callbacks of type CB registered with the calling object
`uvm_do_obj_callbacks_exit_on
Calls the given METHOD of all callbacks of type CB registered with the given object OBJ,
which must be or be based on type T, and returns upon the first callback that returns the bit value
given by VAL.
INTRODUCTION
Verification methodological manual (VMM) , co-authored by verification experts from ARM
and Synopsys, describes how to use SystemVerilog to develop scalable, predictable and
reusable verification environments. VMM has become important factor in increasing
verification reuse, improved verification productivity and timeliness.
VMM consists coding guide lines and base classes. VMM is focused on Coverage driven
verification methodology. VMM supports both the top-down and bottom-up approaches. VMM
follows layered test bench architecture to take the full advantage of the automation. The VMM
for SystemVerilog TestBench architecture comprises five layers.
The layered TestBench is the heart of the verification environment in VMM:
signal layer:
This layer connects the TestBench to the RTL design. It consists of interface, clocking, and
modport constructs.
command layer:
This layer contains lower-level driver and monitor components, as well as the assertions. This
layer provides a transaction-level interface to the layer above and drives the physical pins via the
signal layer.
functional layer:
This layer contains higher-level driver and monitor components, as well as the self-checking
structure (scoreboard/tracker).
scenario layer:
This layer uses generators to produce streams or sequences of transactions that are applied to the
functional layer. The generators have a set of weights, constraints or scenarios specified by the
test layer. The randomness of constrained-random testing is introduced within this layer.
test layer:
Tests are located in this layer. Test layer can interact with all the layers. This layer allows to pass
directed commands to functional and command layer.
VMM libraries consists following sub libraries
VMM Standard Library
VMM Register Abstraction Layer (RAL)
VMM Hardware Abstraction Layer (HAL)
VMM Scoreboarding
The VMM Standard Library provides base classes for key aspects of the verification
environment, transaction generation, notification service and a message logging service.
These libraries can be downloaded from http://www.vmmcentral.org
Following are some of the classes and macros defined in the VMM Standard Library
vmm_env :
The class is a base class used to implement verification environments.
vmm_xactor :
This base class is to be used as the basis for all transactors, including bus-functional models,
monitors and generators. It provides a standard control mechanism expected to be found in all
transactors.
vmm_channel :
This class implements a generic transaction-level interface mechanism. Transaction-level
interfaces remove the higher-level layers from the physical interface details. Using channels,
transactors pass transactions from one to other.
vmm_data :
This base class is to be used as the basis for all transaction descriptors and data models. It
provides a standard set of methods expected to be found in all descriptors. User must extend
vmm_data to create a custom transaction.
vmm_log :
The vmm_log class used implements an interface to the message service. These classes provide a
mechanism for reporting simulation activity to a file or a terminal. To ensure a consistent look
and feel to the messages issued from different sources, vmm_log is used.
vmm_atomic_gen :
This is a macro. This macro defines a atomic generator for generating transaction which are
derived from vmm_data.
vmm_scenario_gen :
Defines a scenario generator class to generate sequences of related instances of the specified
class.
vmm_notify :
The vmm_notify class implements an interface to the notification service. The notification
service provides a synchronization mechanism for concurrent threads or transactors.
vmm_test :
This class will be useful for runtime selection of testcases to run on an environment.
VMM LOG
The vmm_log class provides an interface to the VMM message service so that all messages,
regardless of their sources, can have a common "look and feel". Not always we require all type
of messages. During normal simulation, we need only note, error and warning messages. While
you are debugging, you may need debug messages and trace messages. vmm_log allows you to
control the messages. This helps in debugging. vmm_log has a uniform message format which
helps for post processing the log file using scripts if needed. You can also convert a error
message to warning message which is required in some specific testcases. The message service
describes and controls messages based on several concepts:
Message source:
A message source can be any component of a TestBench. They can be Transactors, scoreboards,
assertions, environment or a testcase.
Message filters:
Filters can prevent or allow a message from being issued. They can be promoted or demoted
based on the identifier, type, severity or content.
Vmm Message Type
Individual messages are categorized into different types.
FAILURE_TYP : For reporting error messages.
NOTE_TYP : Normal message used to indicate the simulation progress.
DEBUG_TYP : Message useful for debugging purpose.
TIMING_TYP : For reporting timing errors.
XHANDLING_TYP: For reporting X or Z on signals
INTERNAL_TYP : Messages from the VMM base classes.
REPORT_TYP,PROTOCOL_TYP,TRANSACTION_TYP,COMMAND_TYP,CYCLE_TYP
: Additional message types that can be used by transactors.
Message Severity
Individual messages are categorized into different severities
FATAL_SEV : An error which causes a program to abort.
ERROR_SEV : Simulation aborts after a certain number of errors are observed.
WARNING_SEV: Simulation can proceed and still produce useful result.
NORMAL_SEV : This message indicates the state of simulation.
TRACE_SEV : This message identifies high-level internal information that is not normally
issued.
DEBUG_SEV : This message identifies medium-level internal information that is not
normally issued.
VERBOSE_SEV: This message identifies low-level internal information that is not normally
issued.
Vmm Log Macros
We can use the following predefined macros to select the message severity.
`vmm_fatal(vmm_log log, string msg);
`vmm_error(vmm_log log, string msg);
`vmm_warning(vmm_log log, string msg);
`vmm_note(vmm_log log, string msg);
`vmm_trace(vmm_log log, string msg);
`vmm_debug(vmm_log log, string msg);
`vmm_verbose(vmm_log log, string msg);
The second argument in the above macro can accept only a string. So if we want to pass a
complex message, then convert it to string using a $psprintf() system task.
These macros are simple to use and the macro expansion is easy to understand. Following is a
expansion of `vmm_not macro. Other macros also have same logic with different Message
severity levels.
`define vmm_note ( log, msg )
do
if (log.start_msg(vmm_log::NOTE_TYP)) begin
void'(log.text(msg));
log.end_msg();
end
while (0)
To change the severity and verbosity of the messages services at simulation time , use
+vmm_log_default=SEVERITY TYPE. Where SEVERITY TYPE can be ERROR, WARNING,
NORMAL, TRACE, DEBUG OR VERBOSE. We will see the demo of this service in the
example.
Message Handling
Different messages require different action by the simulator once the message has been issued.
ABORT_SIM : Aborts the simulation.
COUNT_ERROR: Count the message as an error.
STOP_PROMPT: Stop the simulation immediately and return to the simulation runtime-
control command prompt.
DEBUGGER : Stop the simulation immediately and start the graphical debugging
environment.
DUMP_STACK : Dump the call stack or any other context status information and continue
the simulation.
CONTINUE : Continue the simulation normally.
To use this vmm feature, take the instance of vmm_log and use the message macros. For most of
the vmm classes like vmm_env,vmm_xactor,vmm_data etc, this instantiation is already done
internally, so user dont need to take a separate instance of vmm_log.
vmm_log log = new("test_log","log");
Following is a simple program which demonstrates the usages of different severity levels. This
program creates the object of a vmm_log class and uses log macros to define various messages.
You can also see the usage of $psprintf() system task.
program test_log();
vmm_log log = new("test_log","log");
initial begin
`vmm_error(log,"This is a ERROR Message");
`vmm_warning(log,"This is a WARNING Message");
`vmm_note(log,$psprintf("This is a NOTE Message at time %d",$time));
`vmm_trace(log,"This is a TRACE Message");
`vmm_debug(log,"This is a DEBUG Message");
`vmm_verbose(log,"This is a VERBOSE Message");
`vmm_fatal(log,"This is a FATAL Message");
end
endprogram
!ERROR![FAILURE] on test_log(log) at 0:
This is a ERROR Message
WARNING[FAILURE] on test_log(log) at 0:
This is a WARNING Message
Normal[NOTE] on test_log(log) at 0:
This is a NOTE Message at time 0
Trace[DEBUG] on test_log(log) at 0:
This is a TRACE Message
Debug[DEBUG] on test_log(log) at 0:
This is a DEBUG Message
*FATAL*[FAILURE] on test_log(log) at 0:
This is a FATAL Message
!ERROR![FAILURE] on test_log(log) at 0:
This is a ERROR Message
WARNING[FAILURE] on test_log(log) at 0:
This is a WARNING Message
Normal[NOTE] on test_log(log) at 0:
This is a NOTE Message at time 0
Trace[DEBUG] on test_log(log) at 0:
This is a TRACE Message
Debug[DEBUG] on test_log(log) at 0:
This is a DEBUG Message
Verbose[DEBUG] on test_log(log) at 0:
This is a VERBOSE Message
*FATAL*[FAILURE] on test_log(log) at 0:
This is a FATAL Message
Counting Number Of Messages Based Of Message Severity
Some time we need to count the number of messages executed based of the severity type.
vmm_log has get_message_count() function which returns the message count based on severity
type, source and message string.
user dont need to implement a logic to print the state of test i.e TEST PASSED or TEST
FAILED, vmm_env has already implemented this in the report() method. We will see this in next
topic.
virtual function int get_message_count(int severity = ALL_SEVS,
string name = "",
string instance = "",
bit recurse = 0);
Following is a example which demonstrations the get_message_count() function. The following
example , also demonstrates the use of $psprintf which will be use full to print message when
there are variable arguments to print.
program test_log();
vmm_log log = new("test_log","log");
int fatal_cnt ;
int error_cnt ;
int warn_cnt ;
initial begin
`vmm_note(log,$psprintf("This is a NOTE Message at time %d",$time));
vmm_log_2.tar
Browse the code in vmm_log_2.tar
Simulation commands
vcs -sverilog -f filelist -R -ntb_opts rvm -ntb_opts dtm
file report:
!ERROR![FAILURE] on test_log(log) at 8:
Packet with CRC ERROR is received
-- Rcvd CRC ERROR message at 8 --
!ERROR![FAILURE] on test_log(log) at 8:
Packet with CRC ERROR is received
-- Rcvd CRC ERROR message at 8 --
!ERROR![FAILURE] on test_log(log) at 11:
Packet with CRC ERROR is received
-- Rcvd CRC ERROR message at 11 --
!ERROR![FAILURE] on test_log(log) at 11:
Packet with CRC ERROR is received
-- Rcvd CRC ERROR message at 11 –
VMM ENV
Verification environment is developed by extending vmm_env class. The TestBench simulation
needs some systematic flow like reset, initialize etc. vmm_env base class has methods formalize
the simulation steps. All methods are declared as virtual methods. All the Verification
components and instantiated, connected and component activities starting is done in this class.
As it contains all verification components instances, the environment class affects the entire test
environment.
The vmm_env class divides a simulation into the following steps, with corresponding methods:
gen_cfg() :
Randomize test configuration parameters.
build() :
Creates the instances of channels and transactors. Transactors are connected using channels.
DUT is also connected to TestBench using the interfaces.
reset_dut() :
Reset the DUT using the interface signals.
cfg_dut() :
Configures the DUT configuration parameters.
start() :
Starts all the components of the verification environment to start component activity. All the
transactors (atomic_gen, senario_gen, vmm_xactor....) have start method, which starts their
activities. All the components start() methods are called in this method.
wait_for_end() :
This method waits till the test is done.
stop() :
Stops all the components of the verification environment to terminate the simulation cleanly.
Stop data generators.
cleanup() :
Performs clean-up operations to let the simulation terminate gracefully. It waits for DUT to
drain all the data it has.
report() :
The report method in vmm_env collects error and warning metrics from all the log objects and
reports a summary of the results.
To create a user environment, define a new class extended from vmm_env and extend the above
methods. To retain the core functionality of the base class methods, each extended method must
call super. as the first line of code.
In The following example, we are defining a new class Custom_env from vmm_env and
extending all the vmm_env class methods. All the methods are extended and `vmm_note()
message is included to understand the simulation flow.
function new();
super.new("Custom_env");
endfunction
endclass
In addition to the methods that have already been discussed earlier, vmm_env also contains a
run() method which does not require any user extension. This method is called from the testcase.
When this method is called, individual steps in vmm_env are called in a sequence in the
following order:
gen_cfg => build => cfg_dut_t => start_t => wait_for_end_t => stop_t => cleanup_t => report
Now we will see the testcase implementation. VMM recommends the TestBench to be
implemented in program block. In a program block, create an object of the Custom_env class
and call the run() method.
program test();
Custom_env env = new();
initial
env.run();
endprogram
Download the files
vmm_env_1.tar
Browse the code in vmm_env_1.tar
Simulation commands
vcs -sverilog -f filelist -R -ntb_opts rvm -ntb_opts dtm
Log file report:
Normal[NOTE] on Custom_env() at 0:
Start of gen_cfg() method
Normal[NOTE] on Custom_env() at 0:
End of gen_cfg() method
Normal[NOTE] on Custom_env() at 0:
Start of build() method
Normal[NOTE] on Custom_env() at 0:
End of build() method
Normal[NOTE] on Custom_env() at 0:
Start of reset_dut() method
Normal[NOTE] on Custom_env() at 0:
End of reset_dut() method
Normal[NOTE] on Custom_env() at 0:
Start of cfg_dut() method
Normal[NOTE] on Custom_env() at 0:
End of cfg_dut() method
Normal[NOTE] on Custom_env() at 0:
Start of start() method
Normal[NOTE] on Custom_env() at 0:
End of start() method
Normal[NOTE] on Custom_env() at 0:
Start of wait_for_end() method
Normal[NOTE] on Custom_env() at 0:
End of wait_for_end() method
Normal[NOTE] on Custom_env() at 0:
Start of stop() method
Normal[NOTE] on Custom_env() at 0:
End of stop() method
Normal[NOTE] on Custom_env() at 0:
Start of cleanup() method
Normal[NOTE] on Custom_env() at 0:
End of cleanup() method
Simulation PASSED on /./ (/./) at 0 (0 warnings, 0 demoted errors & 0 demoted
warnings)
Normal[NOTE] on Custom_env() at 0:
Start of report() method
Normal[NOTE] on Custom_env() at 0:
End of report() method
$finish at simulation time 0
Log file report shows that individual methods in vmm_env are called in a ordered sequence upon
calling run() method.
When you call build() and if gen_cfg() is not called before that, gen_cfg() will be called first then
build will execute.
Same way, if you call gen_cfg() followed by cfg_dut() followed by run(), then cfg_dut() will
make sure to call build() followed by reset_dut() first before executing its user defined logic,
run() will make sure to call start(), wait_for_end(), stop(), clean(), report()in the order given.
program test();
vmm_env env = new();
initial
begin
$display("*************** Before Calling env.gen_cfg() ***************");
env.gen_cfg();
$display("*************** Before Calling env.cfg_dut() ***************");
env.cfg_dut();
$display("*************** Before Calling env.run() ***************");
env.run();
end
endprogram
Download the files
vmm_env_2.tar
Browse the code in vmm_env_2.tar
Simulation commands
vcs -sverilog -f filelist -R -ntb_opts rvm -ntb_opts dtm +vmm_log_default=VERBOSE
Log file report:
*************** Before Calling env.gen_cfg() ***************
Trace[INTERNAL] on Verif Env() at 0:
Generating test configuration...
*************** Before Calling env.cfg_dut() ***************
Trace[INTERNAL] on Verif Env() at 0:
Building verification environment...
Trace[INTERNAL] on Verif Env() at 0:
Reseting DUT...
Trace[INTERNAL] on Verif Env() at 0:
Configuring...
*************** Before Calling env.run() ***************
Trace[INTERNAL] on Verif Env() at 0:
Starting verification environment...
Trace[INTERNAL] on Verif Env() at 0:
Saving RNG state information...
Trace[INTERNAL] on Verif Env() at 0:
Waiting for end of test...
Trace[INTERNAL] on Verif Env() at 0:
Stopping verification environment...
Trace[INTERNAL] on Verif Env() at 0:
Cleaning up...
Simulation PASSED on /./ (/./) at 0 (0 warnings, 0 demoted errors & 0 demoted
warnings)
VMM DATA
vmm_data class is to be used to model all transactions in the infrastructure . It provides a
standard set of methods expected to be found in all transactions. All transactions in the
verification environment inherit this object and override its main generic virtual tasks such as
copy(), byte_pack(), byte_unpack(), compare() and psdisplay().
This class is used to generate random, constraint random and directed transactions.
endfunction
virtual function vmm_data copy(vmm_data to = null);
Packet cpy;
// Copying to a new instance?
if (to == null)
cpy = new;
else
// Copying to an existing instance. Correct type?
if (!$cast(cpy, to))
begin
`vmm_fatal(this.log, "Attempting to copy to a non packet instance");
copy = null;
return copy;
end
super.copy_data(cpy);
cpy.da = this.da;
cpy.sa = this.sa;
cpy.length = this.length;
cpy.data = new[this.data.size()];
foreach(data[i])
begin
cpy.data[i] = this.data[i];
end
cpy.fcs = this.fcs;
copy = cpy;
endfunction:copy
virtual function bit compare(input vmm_data to,output string diff,input int kind= -1);
Packet cmp;
compare = 1; // Assume success by default.
diff = "No differences found";
if (!$cast(cmp, to))
begin
`vmm_fatal(this.log, "Attempting to compare to a non packet instance");
compare = 0;
diff = "Cannot compare non packets";
return compare;
end
// data types are the same, do comparison:
if (this.da != cmp.da)
begin
diff = $psprintf("Different DA values: %b != %b", this.da, cmp.da);
compare = 0;
return compare;
end
if (this.sa != cmp.sa)
begin
diff = $psprintf("Different SA values: %b != %b", this.sa, cmp.sa);
compare = 0;
return compare;
end
if (this.length != cmp.length)
begin
diff = $psprintf("Different LEN values: %b != %b", this.length, cmp.length);
compare = 0;
return compare;
end
foreach(data[i])
if (this.data[i] != cmp.data[i])
begin
diff = $psprintf("Different data[%0d] values: 0x%h != 0x%h",i, this.data[i], cmp.data[i]);
compare = 0;
return compare;
end
if (this.fcs != cmp.fcs)
begin
diff = $psprintf("Different FCS values: %b != %b", this.fcs, cmp.fcs);
compare = 0;
return compare;
end
endfunction:compare
virtual function int unsigned byte_pack(
ref logic [7:0] bytes[],
input int unsigned offset =0 ,
input int kind = -1);
byte_pack = 0;
bytes = new[this.data.size() + 4];
bytes[0] = this.da;
bytes[1] = this.sa;
bytes[2] = this.length;
foreach(data[i])
bytes[3+i] = data[i];
bytes[this.data.size() + 3 ] = fcs;
byte_pack = this.data.size() + 4;
endfunction:byte_pack
virtual function int unsigned byte_unpack(
const ref logic [7:0] bytes[],
input int unsigned offset = 0,
input int len = -1,
input int kind = -1);
this.da = bytes[0];
this.sa = bytes[1];
this.length = bytes[2];
this.fcs = bytes[bytes.size() -1];
this.data = new[bytes.size() - 4];
foreach(data[i])
this.data[i] = bytes[i+3];
return bytes.size();
endfunction:byte_unpack
endclass
Vmm_data Methods
Channels are similar to SystemVerilog mailboxes with advanced features. Vmm Channels
provides much richer feature functionality than a SV mailbox. vmm channels are superset of
mailboxs.
Some of the the benefits of channels over mailboxes:
Dynamic reconfiguration
Inbuilt notifications
Strict type checking
Out of order usage
task tee() for easy scoreboarding
Record and playback
task sneak() for monitors
Using `vmm_channel() macro , channels can be created.
`vmm_channel(Custom_vmm_data)
The above macro creates a channel Custom_vmm_data_channel . There are various methods to
access the channels.
In the following example, we will see
1) Channel creation using macros.
2) Constructing a channel.
3) Pushing a transaction in to channel.
4) Popping out a transaction from the channel.
We will create a channel for vmm_data for this example. Users can create a channel for any
transaction which is derived from the vmm_data class. You can try this example by creating
channel for Packet class which is discussed in previous section.
1) Define a channel using macro
`vmm_channel(vmm_data)
p_c.put(p_put);
p_c.get(p_put);
Complete Example
`vmm_channel(vmm_data)
program test_channel();
vmm_data p_put,p_get;
vmm_data_channel p_c = new("p_c","chan",10);
int i;
initial
repeat(10)
begin
#( $urandom()%10);
p_put = new(null);
p_put.stream_id = i++;
$display(" Pushed a packet in to channel with id %d",p_put.stream_id);
p_c.put(p_put); // Pushing a transaction in to channel
end
initial
forever
begin
p_c.get(p_get); // popping a transaction from channel.
$display(" Popped a packet from channel with id %d",p_get.stream_id);
end
endprogram
Download the file
vmm_channel.tar
Browse the code in vmm_channel.tar
Command to run the simulation
vcs -sverilog -f filelist -R -ntb_opts rvm -ntb_opts dtm
Log report
Pushed a packet in to channel with id 0
Popped a packet from channel with id 0
Pushed a packet in to channel with id 1
Popped a packet from channel with id 1
Pushed a packet in to channel with id 2
Popped a packet from channel with id 2
Pushed a packet in to channel with id 3
Popped a packet from channel with id 3
Pushed a packet in to channel with id 4
Popped a packet from channel with id 4
Pushed a packet in to channel with id 5
Popped a packet from channel with id 5
Pushed a packet in to channel with id 6
Popped a packet from channel with id 6
Pushed a packet in to channel with id 7
Popped a packet from channel with id 7
Pushed a packet in to channel with id 8
Popped a packet from channel with id 8
Pushed a packet in to channel with id 9
Popped a packet from channel with id 9
Vmm Channel Methods.
function new ( string name, string instance,
int unsigned full = 1, int unsigned empty = 0, bit fill_as_bytes = 0 );
function void reconfigure (int full = -1, int empty = -1, logic fill_as_bytes = 1'bx );
function int unsigned full_level ( );
function int unsigned empty_level ( );
function int unsigned level ( );
function int unsigned size ( );
function bit is_full ( );
function void flush ( );
function void sink ( );
function void flow ( );
function void lock ( bit [1:0] who );
function void unlock ( bit [1:0] who );
function bit is_locked ( bit [1:0] who );
task put ( class_name obj, int offset = -1 );
function void sneak ( class_name obj, int offset = -1 );
function class_name unput ( int offset = -1 );
task get ( output class_name obj, input int offset = 0 );
task peek ( output class_name obj, input int offset = 0 );
task activate ( output class_name obj, input int offset = 0 );
function class_name active_slot ( );
function class_name start ( );
function class_name complete ( vmm_data status = null );
function class_name remove ( );
function active_status_e status ( );
task tee ( output class_name obj );
function bit tee_mode ( bit is_on );
function void connect ( vmm_channel downstream );
function class_name for_each ( bit reset = 0 );
function int unsigned for_each_offset ( );
function bit record ( string filename );
task bit playback ( output bit success, input string filename,
input vmm_data loader, input bit metered = 0 );
The generator will stop generating the transaction after generating stop_after_n_insts. User can
set the stop_after_n_insts to any unsigned int value. By default this values is 0.
2) define `vmm_channel for the packet class. This macro creates a packet_channel which will be
used by the packet_atomic_gen to store the transactions. Any other component can take the
transactions from this channel.
`vmm_channel(packet)
3) Create an object of pcakt_atomic_gen.
packet_atomic_gen pkt_gen = new("Atomic Gen","test");
4) Set the number of transactions to be generated to 4
pkt_gen.stop_after_n_insts = 4;
5) Start the generator to generate transactions. These transactions are available to access through
pkt_chan as soon as they are generated.
pkt_gen.start_xator();
6) Collect the packets from the pkt_chan and display the packet content to terminal.
pkt_gen.out_chan.get(pkt);
pkt.display();
Completed Example
`vmm_channel(Packet)
`vmm_atomic_gen(Packet,"Atomic Packet Generator")
program test_atomic_gen();
Packet_atomic_gen pkt_gen = new("Atomic Gen","test");
Packet pkt;
initial
begin
pkt_gen.stop_after_n_insts = 4;
#100; pkt_gen.start_xactor();
end
initial
#200 forever
begin
pkt_gen.out_chan.get(pkt);
pkt.display();
end
endprogram
Download the example
vmm_atomic_gen.tar
Browse the code in vmm_atomic_gen.tar
Commands to run the simulation
vcs -sverilog -f filelist -R -ntb_opts rvm -ntb_opts dtm
Log file report
packet #1952805748.0.0
da:0xdb
sa:0x71
length:0x0e (data.size=1)
data[0]:0x63
fcs:0xe9
packet #1952805748.0.1
da:0xa7
sa:0x45
length:0xa4 (data.size=5)
data[0]:0x00 data[1]:0x4f .... data[3]:0xe7 data[4]:0xd8
fcs:0x31
packet #1952805748.0.2
da:0x15
sa:0xe6
length:0xa1 (data.size=1)
data[0]:0x80
fcs:0x01
packet #1952805748.0.3
da:0xd7
sa:0xa9
length:0xdc (data.size=3)
data[0]:0xcc data[1]:0x7c data[1]:0x7c
fcs:0x67
VMM XACTOR
This base class is to be used as the basis for all transactors, including bus-functional models,
monitors and generators. It provides a standard control mechanism expected to be found in all
transactors.
VMM CALLBACK
Callback mechanism is used for altering the behavior of the transactor without modifying the
transactor. One of the many promises of Object-Oriented programming is that it will allow for
plug-and-play re-usable verification components. Verification Designers will hook the
transactors together to make a verification environment. In SystemVerilog, this hooking together
of transactors can be tricky. Callbacks provide a mechanism whereby independently developed
objects may be connected together in simple steps.
This article describes vmm callbacks. Vmm callback might be used for simple notification, two-
way communication, or to distribute work in a process. Some requirements are often
unpredictable when the transactor is first written. So a transactor should provide some kind of
hooks for executing the code which is defined afterwards. In vmm, these hooks are created using
callback methods. For instance, a driver is developed and an empty method is called before
driving the transaction to the DUT. Initially this empty method does nothing. As the
implementation goes, user may realize that he needs to print the state of the transaction or to
delay the transaction driving to DUT or inject an error into transaction. Callback mechanism
allows executing the user defined code in place of the empty callback method. Other example of
callback usage is in monitor. Callbacks can be used in a monitor for collecting coverage
information or for hooking up to scoreboard to pass transactions for self checking. With this,
user is able to control the behavior of the transactor in verification environment and individual
testcases without doing any modifications to the transactor itself.
Following are the steps to be followed to create a transactor with callbacks. We will see simple
example of creating a Driver transactor to support callback mechanism.
endclass
2) Calling callback method.
Inside the transactor, callback methods should be called whenever something interesting
happens.
We will call the callback method before driving the packet and after driving the packet. We
defined 2 methods in facade class. We will call pre_send() method before sending the packet and
post_send() method after sending the packet.
Place the above macros before and after driving the packet.
virtual task main();
super.main();
forever begin
`vmm_callback(Driver_callbacks,pre_send());
$display(" Driver: Started Driving the packet ...... %d",$time);
// Logic to drive the packet goes hear
// let's consider that it takes 40 time units to drive a packet.
#40;
$display(" Driver: Finished Driving the packet ...... %d",$time);
`vmm_callback(Driver_callbacks,post_send());
end
endtask
With this, the Driver implementation is completed with callback support.
Complete Source Code
class Driver_callbacks extends vmm_xactor_callbacks;
Log report
CB_1:pre_send: Delaying the packet driving by 20 time units. 100
Driver: Started Driving the packet ...... 120
Driver: Finished Driving the packet ...... 160
CB_1:post_send: Just a message from post send callback method
endclass
endclass
Custom_Driver_callbacks_1 CDc_1 = new();
Custom_Driver_callbacks_2 CDc_2 = new();
initial
begin
drvr.append_callback(CDc_1);
drvr.append_callback(CDc_2);
#100 drvr.start_xactor();
#200 drvr.stop_xactor();
end
endprogram
vmm_callback_2.tar
Browse the code in vmm_callback_2.tar
drvr.append_callback(CDc_1);
drvr.append_callback(CDc_2);
Now we will see how to change the order of the callback method calls.
Use prepend_callback() method for registering instead of append_callback() method.
Vmm also provides method to remove the callback methods which are registered.
VMM TEST
vmm_test is introduced in vmm 1.1.
To know the vmm version which you are using, use this command
vcs -R -sverilog -ntb_opts dtm
+incdir+$VMM_HOME/sv $VMM_HOME/sv/vmm_versions.sv
vmm_test is used for compiling all the testcases in one compilation. The simulation of each
testcase is done individually. Traditionally for each testcase, compile and simulation are done per
testcase file. With this new approach, which dose compilation only once, will save lot of cup.
Generally each testcase can be divided into two parts.
Procedural code part.
The procedural code part (like passing the above new constrained transaction definition to some
atomic generator, calling the env methods etc) has to be defined between these macros. vmm
provides 2 macros to define testcase procedural part.
`vmm_test_begin(testcase_name,vmm_env,"Test Case Name String")
`vmm_test_env(testcase_name)
Declarative code part.
The declarative part( like new constrained transacting definition) is defined outside these
macros.
Writing A Testcase
Procedural part:
Use a `vmm_test_begin . There are 3 arguments to macro.
The first argument is the name of the testcase class and will also be used as the name of the
testcase in the global testcase registry.
The second argument is the name of the environment class that will be used to execute the
testcase. A data member of that type named "env" will be defined and assigned, ready to be
used.
Log file
RECORDING started
trans (1.0.0):
DA='haecd55f5b651
SA='h13db0b1a590e
length='hd8a7d371
kind=GOOD_FCS
trans (1.0.1):
DA='h5e980e9b7ce9
SA='h44a4bbdaf703
length='h5714c3a7
kind=GOOD_FCS
trans (1.0.2):
DA='hba4f5a021300
SA='h2de750b6cc3e
length='h22ca2bd8
kind=GOOD_FCS
You can also see a file named "Trans_recordes.tr" in your simulation directory.
Command to playback the transaction
PLAYBACK started
trans (0.0.0):
DA='haecd55f5b651
SA='h13db0b1a590e
length='hd8a7d371
kind=GOOD_FCS
trans (0.0.0):
DA='h5e980e9b7ce9
SA='h44a4bbdaf703
length='h5714c3a7
kind=GOOD_FCS
trans (0.0.0):
DA='hba4f5a021300
SA='h2de750b6cc3e
length='h22ca2bd8
kind=GOOD_FCS
Look, the same transactions which were generated while RECORDING are played back.
VMM SCENARIO GENERATOR
Atomic generator generates individual data items or transaction descriptors. Each item is
generated independently of other items in a random fashion. Atomic generator is simple to
describe and use.
Unlike atomic generator, a scenario generator generates a sequence of transaction.
Example of sequence of transaction:
It is very unlikely that atomic generator to generate transaction in the above ordered sequence.
With scenario generator, we can generate these sequence of transaction.
VMM provides `vmm_scenario_gen() macro for quickly creating a scenario generator.
`vmm_scenario_gen(class_name, "Class Description");
The macro defines classes named <class_name>_scenario_gen, <class_name>_scenario,
<class_name>_scenario_election , <class_name>_scenario_gen_callbacks and
<class_name>_atomic_scenario.
<class_name>_scenario:
This scenario generator can generate more than one scenario. Each scenario which can contain
more than one transaction is described in a class which is extended from
<class_name>_scenario.
For each scenario, following variables must be constraint.
Length: number of transactions in an array.
Repeated: number of times to repeat this scenario.
<class_name>_atomic_scenario:
This class is a atomic scenario. This is the default scenario. This scenario is a random
transactions.
<class_name>_scenario_election:
This class is the arbiter which determines the order that the known scenarios are applied. By
default, scenarios are elected atomically. User can extend this class to define an order in which
the scenarios should be picked.
<class_name>_scenario_gen:
This class is the scenario generator which generates the transactions and sends out using output
channel. This class has a queue of scenario objects. Each scenario contains transaction instances
in an array.
<class_name>_scenario_gen_callbacks:
This class provides callback mechanism. There are two callback methods define.
pre_scenario_randomize() and post_scenario_gen() which are called at pre-randomization of the
scenario and post-generation of the scenario respectively.
Example
Let us write an example.
Following is the transaction class which we will use to write a scenario generator.
class instruction extends vmm_data;
vmm_log log;
typedef enum {LOAD__A,LOAD__B,ADD_A_B,SUB_A_B,STORE_C } kinds_e;
rand kinds_e inst;
function new();
super.new(this.log);
endfunction:new
virtual function string psdisplay(string prefix = "");
psdisplay = $psprintf(" Instruction : %s | stream_id : %0d | scenario_id : %0d
",inst.name(),stream_id,scenario_id);
endfunction:psdisplay
virtual function vmm_data allocate();
instruction tr = new;
allocate = tr;
endfunction:allocate
virtual function vmm_data copy(vmm_data cpy = null);
instruction to;
if (cpy == null)
to = new;
else
if (!$cast(to, cpy)) begin
`vmm_fatal(this.log, "Attempting to copy to a non instruction instance");
return null;
end
super.copy_data(to);
to.inst = this.inst;
copy = to;
endfunction:copy
endclass
The above transaction crests CPU instructions.
Let us consider that sequence of instruction LOAD__A,LOAD__B,ADD_A_B,STORE_C is a
interesting scenario and LOAD__A,LOAD__B,SUB_A_B,STORE_C is also an interesting
scenario.
When instructions are generated, we want to generate these 2 sequences of instruction. Let us see
how to generate these 2 sequence of instructions .
As we have already discussed, `vmm_scenario_gen() creates use full class for cscenario
generation.
1) Use `vmm_scenario_gen() macro to declare the scenario classes.
`vmm_scenario_gen(instruction, "Instruction Scenario Generator")
This macro will create following classs
instruction_scenario_gen
instruction_scenario
instruction_atomic_scenario
instruction_scenario_election
instruction_scenario_gen_callbacks
2) Define interesting scenarios by extending inst_scenario;
class instruction_scenario_add_sub extends instruction_scenario;
3) Define the first scenario. It is sequence of instructions for addition operation.
a) Declare a variable for identifying the scenario.
int addition_scenario_id ;
Each scenario has more than one inductions. All these instructions are in a queue "items"
which is already defined in "instruction_scenario".
The rand varible "scenario_kind", which pre defined in the vmm_scenario class, will randomly
select one of the defined scenarios. "scenario_kind" varible has the id of the current scenario. So
we have to define the addition scenario, when the "scenario_kind" value is addition_scenario_id.
b) Constrain the scenario kind,
constraint addition_scenario_items {
if($void(scenario_kind) == addition_scenario_id) {
c) The number of instructions define in a scenario is specified by the predefined variable
"length". In our example, we have 4 instructions. So constrain the length to 4.
length == 4;
d) The predefined variable "repeated" used to control if the VMM scenario generator would run
the scenario more than once each time it is created. We are not interested in repeating so,
constrain it to 0
repeated == 0;
e) Constrain the individual items based on the requirements if this scenario is selected. Our
requirement in this example is that "inst" should follow the sequence
LOAD__A,LOAD__B,ADD_A_B,STORE_C
foreach(items[i])
if(i == 0)
this.items[i].inst == instruction::LOAD__A;
else if(i == 1)
this.items[i].inst == instruction::LOAD__B;
else if(i == 2)
this.items[i].inst == instruction::ADD_A_B;
else if(i == 3)
this.items[i].inst == instruction::STORE_C;
///////////////////////////////////////////////////////////////
////// ADDITION SCENARIO //////////
///////////////////////////////////////////////////////////////
int addition_scenario_id ;
constraint addition_scenario_items {
if($void(scenario_kind) == addition_scenario_id) {
repeated == 0;
length == 4;
foreach(items[i])
if(i == 0)
this.items[i].inst == instruction::LOAD__A;
else if(i == 1)
this.items[i].inst == instruction::LOAD__B;
else if(i == 2)
this.items[i].inst == instruction::ADD_A_B;
else if(i == 3)
this.items[i].inst == instruction::STORE_C;
}
}
///////////////////////////////////////////////////////////////
////// ASUBTRACTION SCENARIO //////////
///////////////////////////////////////////////////////////////
int subtraction_scenario_id ;
constraint subtraction_scenario_items {
if($void(scenario_kind) == subtraction_scenario_id) {
repeated == 0;
length == 4;
foreach(items[i])
if(i == 0)
this.items[i].inst == instruction::LOAD__A;
else if(i == 1)
this.items[i].inst == instruction::LOAD__B;
else if(i == 2)
this.items[i].inst == instruction::SUB_A_B;
else if(i == 3)
this.items[i].inst == instruction::STORE_C;
}
}
function new();
this.addition_scenario_id = define_scenario(" ADDITION ",4);
this.subtraction_scenario_id = define_scenario(" SUBSTRACTION ",4);
endfunction
endclass
Testcase
Now we will write a test case to see how to above defined scenario works.
1) Declare scenario generator
instruction_scenario_gen gen;
2) Declare the scenario which we defined earlier.
instruction_scenario_add_sub sce_add_sub;
3) Construct the generator and scenarios.
gen = new("gen",0);
sce_add_sub = new();
4) set the number of instances and scenarios generated by generator to 20 and 4 respectevy.
gen.stop_after_n_insts = 20;
gen.stop_after_n_scenarios = 4;
5) Scenario generators store all the scenarios in scenario_set queue. So, we have to add the
scenario which we constructed above to the queue.
gen.scenario_set[0] = sce_add_sub;
6) Start the generator
gen.start_xactor();
7) Similar t the Atomic generator, the transactions created by the scenario generator are sent to
out_chan channel.
Get the instructions from the out_chan channel and print the content.
repeat(20) begin
gen.out_chan.get(inst);
inst.display();
Testcase code
program test();
instruction_scenario_gen gen;
instruction_scenario_add_sub sce_add_sub;
instruction inst;
initial
begin
gen = new("gen",0);
sce_add_sub = new();
//gen.log.set_verbosity(vmm_log::DEBUG_SEV,"/./","/./");
gen.stop_after_n_insts = 20;
gen.stop_after_n_scenarios = 4;
gen.scenario_set[0] = sce_add_sub;
gen.start_xactor();
repeat(20) begin
gen.out_chan.get(inst);
inst.display();
end
end
endprogram
vmm_scenario.tar
Browse the code in vmm_scenario.tar
Command to simulate
vcs -sverilog -ntb_opts rvm -f filelist -R
Observe the log file, you can see the both the scenarios.
Logfile
VMM OPTS
Some of the test parameters in functional verification are passed from the command line. Passing
Parameters from the command line allows simulating the same testcase file with different
scenario. For example, let say you are verifying Ethernet protocol. Insert_crc_error.sv is a
testcase file which verifies the DUT by sending CRC error packets. Now you can use the same
testcase file to verify the DUT in 3 different modes , 10G 1000M and 100M modes by passing
these parameters from the command prompt, this increases the testcase reusability.
As parameters became one of the most important techniques in reusing the testcases, vmm has
added vmm_opts class to manage parameters efficiently.
vmm_opts captures parameter from run time options.
There are 2 ways to specify the parameters during runtime.
1) Command line
vmm_opts internally uses $plusargs to recognize the options specified on command line.
2) text file
If you have lot of parameters to mentions, then you can also mention all the parameters in a
text file and pass it as command line options.
Following are the 3 static methods which are defined in vmm_opts to get the parameter values.
Static function string get_string(string name, string dflt = "", string doc = "");
The first argument is the name of the parameter and the argument "doc" is description of the
runtime argument that will be displayed by the vmm_opts::get_help() method. as these methods
are static, user dont need to construct the object of vmm_opts.
To supply a integer value "5" for runtime option "foo", use the "+vmm_opts+...+foo=5+..."
command-line option, or "+vmm_foo=5" command-line option, or the line "+foo=5" in the
option file.
To supply a string value "bar" for runtime option "foo" , use the "+vmm_opts+...+foo=bar+..."
command-line option, or "+vmm_foo=bar" command-line option, or the line "+foo=bar" in the
option file.
To supply a textfile which contains the runtime options, use "+vmm_opts_file=finename.txt"
+vmm_help command line option will print the run time options help. This option will call the
vmm_opts::get_help() method.
The following is a basic example of how these parameters could be received using vmm_opts
EXAMPLE
`include "vmm.sv"
class my_env extends vmm_env;
virtual function void gen_cfg();
integer foo1;
string foo2;
bit foo3;
super.gen_cfg();
`vmm_note(log,"START OF GEN_CFG ");
foo1 = vmm_opts::get_int("foo1" , 10 , "Help for the option foo1: pass any integer between 0
to 100");
foo2 = vmm_opts::get_string("foo2" , "default_string" , "Help for the option foo2: pass any
string");
foo3 = vmm_opts::get_int("foo3" , "Help for the option foo3: just use foo3 to enable ");
`vmm_note(log,$psprintf("\n Run time Options \n foo1 : %0d \n foo2 : %s \n
foo3 : %b \n",foo1,foo2,foo3));
`vmm_note(log,"END OF GEN_CFG ");
endfunction
endclass
program main();
initial
begin
my_env env;
env = new();
env.run();
end
endprogram
Download the example
vmm_opts.tar
Browse the code in vmm_opts.tar
Commands to complie
This works with vmm 1.1 and above.
vcs -sverilog -ntb_opts dtm +incdir+$VMM_HOME/sv main_testcase.sv
Commands to complie
./simv
./simv +vmm_foo1=101
./simv +vmm_opts+foo1=101+foo2=Testbench.in
Log
INTRODUCTION
The OVM Class Library provides the building blocks needed to quickly develop well-
constructed and reusable verification components and test environments in SystemVerilog.
In this tutorial, we will learn some of the OVM concepts with examples.
OVM TESTBENCH
Ovm components, ovm env and ovm test are the three main building blocks of a testbench in
ovm based verification.
Ovm_env
Ovm_env is extended from ovm_componented and does not contain any extra functionality.
Ovm_env is used to create and connect the ovm_components like driver, monitors , sequeners
etc. A environment class can also be used as sub-environment in another environment. As there
is no difference between ovm_env and ovm_component , we will discuss about ovm_component,
in the next section.
Verification Components
Ovm verification component classes are derived from ovm_component class which provides
features like hierarchy searching, phasing, configuration , reporting , factory and transaction
recording.
Following are some of the ovm component classes
Ovm_agent
Ovm_monitor
Ovm_scoreboard
Ovm_driver
Ovm_sequencer
OVM phases
OVM Components execute their behavior in strictly ordered, pre-defined phases. Each phase is
defined by its own virtual method, which derived components can override to incorporate
component-specific behavior. By default , these methods do nothing.
--> virtual function void build()
This phase is used to construct various child components/ports/exports and configures them.
In this phase , Main body of the test is executed where all threads are forked off.
In this phase, check the results of the extracted information such as un responded requests in
scoreboard, read statistics registers etc.
Only build() method is executed in top down manner. i.e after executing parent build() method,
child objects build() methods are executed. All other methods are executed in bottom-up
manner. The run() method is the only method which is time consuming. The run() method is
forked, so the order in which all components run() method are executed is undefined.
Ovm_test
Ovm_test is derived from ovm_component class and there is no extra functionality is added. The
advantage of used ovm_test for defining the user defined test is that the test case selection can be
done from command line option +OVM_TESTNAME=<testcase_string> . User can also select
the testcase by passing the testcase name as string to
ovm_root::run_test(<testcase_string>) method.
In the above <testcase_string> is the object type of the testcase class.
Lets implement environment for the following topology. I will describe the implementation of
environment , testcase and top module. Agent, monitor and driver are implemented similar to
environment.
`ovm_component_utils(env)
3)Declare the objects for agents.
agent ag1;
agent ag2;
4)Define the constructor. In the constructor, call the super methods and pass the parent object.
Parent is the object in which environment is instantiated.
6)Define connect(),end_of_elaboration(),start_of_simulation(),run(),extract(),check(),report()
methods.
Just print a message from these methods, as we dont have any logic in this example to define.
function void connect();
ovm_report_info(get_full_name(),"Connect", OVM_LOG);
endfunction
Complete code of environment class:
class env extends ovm_env;
`ovm_component_utils(env)
agent ag1;
agent ag2;
function new(string name, ovm_component parent);
super.new(name, parent);
endfunction
function void build();
ovm_report_info(get_full_name(),"Build", OVM_LOG);
ag1 = agent::type_id::create("ag1",this);
ag2 = agent::type_id::create("ag2",this);
endfunction
function void connect();
ovm_report_info(get_full_name(),"Connect", OVM_LOG);
endfunction
function void end_of_elaboration();
ovm_report_info(get_full_name(),"End_of_elaboration", OVM_LOG);
endfunction
function void start_of_simulation();
ovm_report_info(get_full_name(),"Start_of_simulation", OVM_LOG);
endfunction
task run();
ovm_report_info(get_full_name(),"Run", OVM_LOG);
endtask
function void extract();
ovm_report_info(get_full_name(),"Extract", OVM_LOG);
endfunction
function void check();
ovm_report_info(get_full_name(),"Check", OVM_LOG);
endfunction
function void report();
ovm_report_info(get_full_name(),"Report", OVM_LOG);
endfunction
endclass
Now we will implement the testcase.
1)Extend ovm_test and define the test case
class test1 extends ovm_test;
2)Declare component ustilits using utility macro.
ovm_component_utils(test1)
2)Declare environment class handle.
env t_env;
3)Define constructor method. In the constructor, call the super method and construct the
environment object.
function new (string name="test1", ovm_component parent=null);
super.new (name, parent);
t_env = new("t_env",this);
endfunction : new
4)Define the end_of_elaboration method. In this method, call the print() method. This print()
method will print the topology of the test.
endclass
Top Module:
To start the testbench, run_test() method must be called from initial block.
Run_test() mthod Phases all components through all registered phases.
module top;
initial
run_test();
endmodule
Download the source code
ovm_phases.tar
Browse the code in ovm_phases.tar
Command to run the simulation
your_tool_simulation_command +path_to_ovm_pkg -f filelist +OVM_TESTNAME=test1
Log file:
ovm_test_top.t_env.ag1.drv[ovm_test_top.t_env.ag1.drv]Start_of_simulation
ovm_test_top.t_env.ag1.mon[ovm_test_top.t_env.ag1.mon]Start_of_simulation
ovm_test_top.t_env.ag1[ovm_test_top.t_env.ag1]Start_of_simulation
ovm_test_top.t_env.ag2.drv[ovm_test_top.t_env.ag2.drv]Start_of_simulation
ovm_test_top.t_env.ag2.mon[ovm_test_top.t_env.ag2.mon]Start_of_simulatio
..
..
..
..
Observe the above log report:
1)Build method was called in top-down fashion. Look at the following part of message.
ovm_test_top.t_env [ovm_test_top.t_env] Build
ovm_test_top.t_env.ag1 [ovm_test_top.t_env.ag1] Build
ovm_test_top.t_env.ag1.drv [ovm_test_top.t_env.ag1.drv] Build
ovm_test_top.t_env.ag1.mon [ovm_test_top.t_env.ag1.mon] Build
ovm_test_top.t_env.ag2 [ovm_test_top.t_env.ag2] Build
ovm_test_top.t_env.ag2.drv [ovm_test_top.t_env.ag2.drv] Build
ovm_test_top.t_env.ag2.mon [ovm_test_top.t_env.ag2.mon] Build
2)Connect method was called in bottopm up fashion. Look at the below part of log file,
ovm_test_top.t_env.ag1.drv [ovm_test_top.t_env.ag1.drv] Connect
ovm_test_top.t_env.ag1.mon [ovm_test_top.t_env.ag1.mon] Connect
ovm_test_top.t_env.ag1 [ovm_test_top.t_env.ag1] Connect
ovm_test_top.t_env.ag2.drv [ovm_test_top.t_env.ag2.drv] Connect
ovm_test_top.t_env.ag2.mon [ovm_test_top.t_env.ag2.mon] Connect
ovm_test_top.t_env.ag2 [ovm_test_top.t_env.ag2] Connect
ovm_test_top.t_env [ovm_test_top.t_env] Connect
3)Following part of log file shows the testcase topology.
OVM REPORTING
The ovm_report_object provides an interface to the OVM reporting facility. Through this
interface, components issue the various messages with different severity levels that occur during
simulation. Users can configure what actions are taken and what file(s) are output for individual
messages from a particular component or for all messages from all components in the
environment.
A report consists of an id string, severity, verbosity level, and the textual message itself. If the
verbosity level of a report is greater than the configured maximum verbosity level of its report
object, it is ignored.
Reporting Methods:
Following are the primary reporting methods in the OVM.
virtual function void ovm_report_info
(string id,string message,int verbosity=OVM_MEDIUM,string filename="",int line=0)
verbosity -- the verbosity of the message, indicating its relative importance. If this number is
less than or equal to the effective verbosity level, then the report is issued, subject to the
configured action and file descriptor settings.
filename/line -- If required to print filename and line number from where the message is
issued, use macros, `__FILE__ and `__LINE__.
Actions:
These methods associate the specified action or actions with reports of the givenseverity, id, or
severity-id pair.
Following are the actions defined:
OVM_NO_ACTION -- Do nothing
OVM_DISPLAY -- Display report to standard output
OVM_LOG -- Write to a file
OVM_COUNT -- Count up to a max_quit_count value before exiting
OVM_EXIT -- Terminates simulation immediately
OVM_CALL_HOOK -- Callback the hook method .
Configuration:
Using these methods, user can set the verbosity levels and set actions.
function void set_report_verbosity_level
(int verbosity_level)
function void set_report_severity_action
(ovm_severity severity,ovm_action action)
function void set_report_id_action
(string id,ovm_action action)
function void set_report_severity_id_action
(ovm_severity severity,string id,ovm_action action)
Example
Lets see an example:
In the following example, messages from rpting::run() method are of different verbosity level. In
the top module, 3 objects of rpting are created and different verbosity levels are set using
set_report_verbosity_level() method.
`include "ovm.svh"
import ovm_pkg::*;
class rpting extends ovm_threaded_component;
`ovm_component_utils(rpting)
function new(string name,ovm_component parent);
super.new(name, parent);
endfunction
task run();
ovm_report_info(get_full_name(),
"Info Message : Verbo lvl - OVM_NONE ",OVM_NONE,`__FILE__,`__LINE__);
ovm_report_info(get_full_name(),
"Info Message : Verbo lvl - OVM_LOW ",OVM_LOW);
ovm_report_info(get_full_name(),
"Info Message : Verbo lvl - 150 ",150);
ovm_report_info(get_full_name(),
"Info Message : Verbo lvl - OVM_MEDIUM",OVM_MEDIUM);
ovm_report_warning(get_full_name(),
"Warning Messgae from rpting",OVM_LOW);
ovm_report_error(get_full_name(),
"Error Message from rpting \n\n",OVM_LOG);
endtask
endclass
module top;
rpting rpt1;
rpting rpt2;
rpting rpt3;
initial begin
rpt1 = new("rpt1",null);
rpt2 = new("rpt2",null);
rpt3 = new("rpt3",null);
rpt1.set_report_verbosity_level(OVM_MEDIUM);
rpt2.set_report_verbosity_level(OVM_LOW);
rpt3.set_report_verbosity_level(OVM_NONE);
run_test();
end
endmodule
Download the source code
ovm_reporting.tar
Browse the code in ovm_reporting.tar
Command to run the simulation
your_tool_simulation_command +path_to_ovm_pkg ovm_log_example.sv
Log file:
OVM_INFO reporting.sv(13)@0:rpt1[rpt1]Info Message:Verbo lvl - OVM_NONE
OVM_INFO @0:rpt1[rpt1] Info Message : Verbo lvl - OVM_LOW
OVM_INFO @0:rpt1[rpt1] Info Message : Verbo lvl - 150
OVM_INFO @0:rpt1[rpt1] Info Message : Verbo lvl - OVM_MEDIUM
OVM_WARNIN@0:rpt[rpt1] Warning Messgae from rpting
OVM_ERROR @0:rpt1[rpt1] Error Message from rpting
OVM_INFOreporting.sv(13)@0:rpt2[rpt2]Info Message:Verbo lvl - OVM_NONE
OVM_INFO@ 0:rpt2[rpt2] Info Message : Verbo lvl - OVM_LOW
OVM_WARNING@0:rpt2[rpt2] Warning Messgae from rpting
OVM_ERROR@0:rpt2[rpt2] Error Message from rpting
OVM_INFOreporting.sv(13)@0:rpt3[rpt3]Info Message:Verbo lvl - OVM_NONE
OVM_ERROR @ 9200 [TIMOUT] Watchdog timeout of '23f0' expired.
OVM TRANSACTION
A transaction is data item which is eventually or directly processed by the DUT. The packets,
instructions, pixels are data items. In ovm, transactions are extended from ovm_transactions
class or ovm_sequence_item class. Generally transactions are extended from ovm_transaction if
randomization is not done in sequence and transactions are extended from ovm_sequence_item if
the randomization is done in sequence. In this section, we will see ovm_transaction only,
ovm_sequence_item will be addressed in another section.
Example of a transaction:
Core Utilities:
ovm_transaction class is extended from ovm_object. Ovm_transaction adds more features line
transaction recording , transaction id and timings of the transaction.
The methods used to model, transform or operate on transactions like print, copying, cloning,
comparing, recording, packing and unpacking are already defined in ovm_object.
FIG: OVM OBJECT UTILITIES
User should define these methods in the transaction using do_<method_name> and call them
using <method_name>. Following table shows calling methods and user-defined hook
do_<method_name> methods. Clone and create methods, does not use hook methods concepts.
Shorthand Macros:
Using the field automation concept of ovm, all the above defines methods can be defined
automatically.
To use these field automation macros, first declare all the data fields, then place the field
automation macros between the `ovm_object_utils_begin and `ovm_object_utils_end macros.
Example of field automation macros:
class Packet extends ovm_transaction;
`ovm_object_utils_begin(Packet)
`ovm_field_int(da, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(sa, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(length, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_array_int(data, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(fcs, OVM_ALL_ON|OVM_NOPACK)
`ovm_object_utils_end
endclass.
For most of the data types in systemverilog, ovm defined corresponding field automation
macros. Following table shows all the field automation macros.
Each `ovm_field_* macro has at least two arguments: ARG and FLAG.
ARG is the instance name of the variable and FLAG is used to control the field usage in core
utilities operation.
By default, FLAG is set to OVM_ALL_ON. All these flags can be ored. Using NO_* flags, can
turn of particular field usage in a paerticuler method. NO_* flags takes precedency over other
flags.
Example of Flags:
`ovm_field_int(da, OVM_ALL_ON|OVM_NOPACK)
The above macro will use the field "da" in all utilities methods except Packing and unpacking
methods.
Lets see a example:
In the following example, all the utility methods are defined using field automation macros
except Packing and unpacking methods. Packing and unpacking methods are done in do_pack()
amd do_unpack() method.
`include "ovm.svh"
import ovm_pkg::*;
//Define the enumerated types for packet types
typedef enum { GOOD_FCS, BAD_FCS } fcs_kind_t;
class Packet extends ovm_transaction;
rand fcs_kind_t fcs_kind;
`ovm_object_utils_begin(Packet)
`ovm_field_int(da, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(sa, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(length, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_array_int(data, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(fcs, OVM_ALL_ON|OVM_NOPACK)
`ovm_object_utils_end
da = packer.unpack_field_int($bits(da));
sa = packer.unpack_field_int($bits(sa));
length = packer.unpack_field_int($bits(length));
data.delete();
data = new[length];
foreach(data[i])
data[i] = packer.unpack_field_int(8);
fcs = packer.unpack_field_int($bits(fcs));
endfunction : do_unpack
endclass : Packet
/////////////////////////////////////////////////////////
//// Test to check the packet implementation ////
/////////////////////////////////////////////////////////
module test;
initial
repeat(10)
if(pkt1.randomize)
begin
$display(" Randomization Sucessesfull.");
pkt1.print();
ovm_default_packer.use_metadata = 1;
void'(pkt1.pack_bytes(pkdbytes));
$display("Size of pkd bits %d",pkdbytes.size());
pkt2.unpack_bytes(pkdbytes);
pkt2.print();
if(pkt2.compare(pkt1))
$display(" Packing,Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare *** \n \n");
end
else
$display(" *** Randomization Failed ***");
endmodule
Download the source code
ovm_transaction.tar
Browse the code in ovm_transaction.tar
Command to run the simulation
your_tool_simulation_command +path_to_ovm_pkg -packet.sv
Log report:
Randomization Sucessesfull.
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt1 Packet - pkt1@3
da integral 8 'h1d
sa integral 8 'h26
length integral 8 'h5
data da(integral) 5 -
[0] integral 8 'hb1
[1] integral 8 'h3f
[2] integral 8 'h9e
[3] integral 8 'h38
[4] integral 8 'h8d
fcs integral 8 'h9b
----------------------------------------------------------------------
Size of pkd bits 9
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt2 Packet - pkt2@5
da integral 8 'h1d
sa integral 8 'h26
length integral 8 'h5
data da(integral) 5 -
[0] integral 8 'hb1
[1] integral 8 'h3f
[2] integral 8 'h9e
[3] integral 8 'h38
[4] integral 8 'h8d
fcs integral 8 'h9b
----------------------------------------------------------------------
Packing,Unpacking and compare worked
OVM FACTORY
The factory pattern is an well known object-oriented design pattern. The factory method design
pattern defining a separate method for creating the objects. , whose subclasses can then override
to specify the derived type of object that will be created.
Using this method, objects are constructed dynamically based on the specification type of the
object. User can alter the behavior of the pre-build code without modifying the code. From the
testcase, user from environment or testcase can replace any object which is at any hierarchy level
with the user defined object.
For example: In your environment, you have a driver component. You would like the extend the
driver component for error injection scenario. After defining the extended driver class with error
injection, how will you replace the base driver component which is deep in the hierarchy of
your environment ? Using hierarchical path, you could replace the driver object with the
extended driver. This could not be easy if there are many driver objects. Then you should also
take care of its connections with the other components of testbenchs like scoreboard etc.
One more example: In your Ethernet verification environment, you have different drivers to
support different interfaces for 10mbps,100mps and 1G. Now you want to reuse the same
environment for 10G verification. Inside somewhere deep in the hierarchy, while building the
components, as a driver components ,your current environment can only select
10mmps/100mps/1G drivers using configuration settings. How to add one more driver to the
current drivers list of drivers so that from the testcase you could configure the environment to
work for 10G.
Using the ovm fatroy, it is very easy to solve the above two requirements. Only classs extended
from ovm_object and ovm_component are supported for this.
There are three basic steps to be followed for using ovm factory.
1) Registration
2) Construction
3) Overriding
The factory makes it is possible to override the type of ovm component /object or instance of a
ovm component/object in2 ways. They are based on ovm component/object type or ovm
compoenent/object name.
Registration:
While defining a class , its type has to be registered with the ovm factory. To do this job easier,
ovm has predefined macros.
`ovm_component_utils(class_type_name)
`ovm_component_param_utils(class_type_name #(params))
`ovm_object_utils(class_type_name)
`ovm_object_param_utils(class_type_name #(params))
For ovm_*_param_utils are used for parameterized classes and other two macros for non-
parameterized class. Registration is required for name-based overriding , it is not required for
type-based overriding.
Syntax :
The Create() function returns an instance of the component type, T, represented by this proxy,
subject to any factory overrides based on the context provided by the parents full name. The
context argument, if supplied, supersedes the parents context. The new instance will have the
given leaf name and parent.
EXAMPLE:
class_type object_name;
object_name = class_type::type_id::creat("object_name",this);
For ovm_object based classes, doesnt need the parent handle as second argument.
Overriding:
If required, user could override the registered classes or objects. User can override based of name
string or class-type.
The above method is used to override the object instances of "original_type" with
"override_type" . "override_type" is extended from"original_type".
function void set_inst_override_by_name
(string original_type_name,
string override_type_name,
string full_inst_path )
Original_type_name and override_type_name are the class names which are registered in the
factory. All the instances of objects with name "Original_type_name" will be overriden with
objects of name "override_type_name" using set_inst_override_by_name() method.
3)In this example, a one testcase is already developed in topic OVM_TESTBENCH. There
are no over rides in this test case.
From the testcase , Using set_type_override_by_type, we will override driver with driver_2 and
Using set_type_override_by_name, we will override monitor with monitor_2.
To know about the overrides which are done, call print_all_overrides() method of factory class.
class driver_2 extends driver;
`ovm_component_utils(driver_2)
`ovm_component_utils(monitor_2)
`ovm_component_utils(test_factory)
env t_env;
factory.set_type_override_by_type(driver::get_type(),driver_2::get_type(),"*");
factory.set_type_override_by_name("monitor","monitor_2","*");
factory.print_all_overrides();
t_env = new("t_env",this);
endfunction : new
endclass
Download the example:
ovm_factory.tar
Browse the code in ovm_factory.tar
Command to simulate
Command to run the example with the testcase which is defined above:
Your_tool_simulation_command +incdir+path_to_ovm -f filelist
+OVM_TESTNAME=test_factory
Method factory.print_all_overrides() displayed all the overrides as shown below in the log file.
Type Overrides:
In the below text printed by print_topology() method ,we can see overridden driver and
monitor.
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
ovm_test_top test_factory - ovm_test_top@2
t_env env - t_env@4
ag1 agent - ag1@6
drv driver_2 - drv@12
rsp_port ovm_analysis_port - rsp_port@16
sqr_pull_port ovm_seq_item_pull_+ - sqr_pull_port@14
mon monitor_2 - mon@10
ag2 agent - ag2@8
drv driver_2 - drv@20
rsp_port ovm_analysis_port - rsp_port@24
sqr_pull_port ovm_seq_item_pull_+ - sqr_pull_port@22
mon monitor_2 - mon@18
----------------------------------------------------------------------
In the below text printed by print_topology() method ,with testcase test1 which does note have
overrides.
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
ovm_test_top test1 - ovm_test_top@2
t_env env - t_env@4
ag1 agent - ag1@6
drv driver - drv@12
rsp_port ovm_analysis_port - rsp_port@16
sqr_pull_port ovm_seq_item_pull_+ - sqr_pull_port@14
mon monitor - mon@10
ag2 agent - ag2@8
drv driver - drv@20
rsp_port ovm_analysis_port - rsp_port@24
sqr_pull_port ovm_seq_item_pull_+ - sqr_pull_port@22
mon monitor - mon@18
----------------------------------------------------------------------
OVM SEQUENCE 1
Introduction
A sequence is a series of transaction. User can define the complex stimulus. sequences can be
reused, extended, randomized, and combined sequentially and hierarchically in various ways.
For example, for a processor, lets say PUSH_A,PUSH_B,ADD,SUB,MUL,DIV and POP_C are
the instructions. If the instructions are generated randomly, then to excursing a meaningful
operation like "adding 2 variables" which requires a series of transaction
"PUSH_A PUSH_B ADD POP_C " will take longer time. By defining these series of
"PUSH_A PUSH_B ADD POP_C ", it would be easy to exercise the DUT.
Advantages of ovm sequences :
Sequences can be reused.
Stimulus generation is independent of testbench.
Easy to control the generation of transaction.
Sequences can be combined sequentially and hierarchically.
A complete sequence generation requires following 4 classes.
1- Sequence item.
2- Sequence
3- Sequencer
4- Driver
Ovm_sequence_item :
User has to define a transaction by extending ovm_sequence_item. ovm_sequence_item class
provides the basic functionality for objects, both sequence items and sequences, to operate in the
sequence mechanism. For more information about ovm_sequence_item Refer to link
OVM_TRANSACTION
Ovm_sequence:
User should extend ovm_sequence class and define the construction of sequence of transactions.
These transactions can be directed, constrained randomized or fully randomized. The
ovm_sequence class provides the interfaces necessary in order to create streams of sequence
items and/or other sequences.
virtual class ovm_sequence #(
type REQ = ovm_sequence_item,
type RSP = REQ
)
Ovm_sequencer:
Ovm_sequencer is responsible for the coordination between sequence and driver. Sequencer
sends the transaction to driver and gets the response from the driver. The response transaction
from the driver is optional. When multiple sequences are running in parallel, then sequencer is
responsible for arbitrating between the parallel sequences. There are two types of sequencers :
ovm_sequencer and ovm_push_sequencer
class ovm_sequencer #(
type REQ = ovm_sequence_item,
type RSP = REQ
)
class ovm_push_sequencer #(
type REQ = ovm_sequence_item,
type RSP = REQ
)
Ovm driver:
User should extend ovm_driver class to define driver component. ovm driver is a component that
initiate requests for new transactions and drives it to lower level components. There are two
types of drivers: ovm_driver and ovm_push_driver.
class ovm_driver #(
type REQ = ovm_sequence_item,
type RSP = REQ
)
class ovm_push_driver #(
type REQ = ovm_sequence_item,
type RSP = REQ
)
In pull mode , ovm_sequencer is connected to ovm_driver , in push mode ovm_push_sequencer
is connectd to ovm_push_driver.
Ovm_sequencer and ovm_driver are parameterized components with request and response
transaction types. REQ and RSP types by default are ovm_sequence_type types. User can
specify REQ and RSP of different transaction types. If user specifies only REQ type, then RSP
will be REQ type.
Sequence And Driver Communication:
The above image shows how a transaction from a sequence is sent to driver and the response
from the driver is sent to sequencer. There are multiple methods called during this operation.
If a response from driver is not required, then steps 5,6,7 can be skipped and item_done() method
from driver should be called as shown in above image.
Simple Example
Lest write an example: This is a simple example of processor instruction. Various instructions
which are supported by the processor are PUSH_A,PUSH_B,ADD,SUB,MUL,DIV and POP_C.
Sequence Item
1) Extend ovm_sequence_item and define instruction class.
class instruction extends ovm_sequence_item;
2) Define the instruction as enumerated types and declare a variable of instruction enumerated
type.
typedef enum {PUSH_A,PUSH_B,ADD,SUB,MUL,DIV,POP_C} inst_t;
rand inst_t inst;
3) Define operational method using ovm_field_* macros.
`ovm_object_utils_begin(instruction)
`ovm_field_enum(inst_t,inst, OVM_ALL_ON)
`ovm_object_utils_end
4) Define the constructor.
function new (string name = "instruction");
super.new(name);
endfunction
Sequence item code:
class instruction extends ovm_sequence_item;
typedef enum {PUSH_A,PUSH_B,ADD,SUB,MUL,DIV,POP_C} inst_t;
rand inst_t inst;
`ovm_object_utils_begin(instruction)
`ovm_field_enum(inst_t,inst, OVM_ALL_ON)
`ovm_object_utils_end
endclass
Sequence
We will define a operation addition using ovm_sequence. The instruction sequence should be
"PUSH A PUSH B ADD POP C".
1) Define a sequence by extending ovm_sequence. Set REQ parameter to "instruction" type.
`ovm_sequence_utils(operation_addition, instruction_sequencer)
4)
In the body() method, first call wait_for_grant(), then construct a transaction and set the
instruction enum to PUSH_A . Then send the transaction to driver using send_request() method.
Then call the wait_for_item_done() method. Repeat the above steps for other instructions
PUSH_B, ADD and POP_C.
For construction of a transaction, we will use the create() method.
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::PUSH_B;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::ADD;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::POP_C;
send_request(req);
wait_for_item_done();
//get_response(res);
endtask
Sequence code
class operation_addition extends ovm_sequence #(instruction);
instruction req;
function new(string name="operation_addition");
super.new(name);
endfunction
`ovm_sequence_utils(operation_addition, instruction_sequencer)
virtual task body();
req = instruction::type_id::create("req");
wait_for_grant();
assert(req.randomize() with {
inst == instruction::PUSH_A;
});
send_request(req);
wait_for_item_done();
//get_response(res); This is optional. Not using in this example.
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::PUSH_B;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::ADD;
send_request(req);
wait_for_item_done();
//get_response(res);
req = instruction::type_id::create("req");
wait_for_grant();
req.inst = instruction::POP_C;
send_request(req);
wait_for_item_done();
//get_response(res);
endtask
endclass
Sequencer:
Ovm_sequence has a property called default_sequence. Default sequence is a sequence which
will be started automatically. Using set_config_string, user can override the default sequence to
any user defined sequence, so that when a sequencer is started, automatically a user defined
sequence will be started. If over rides are not done with user defined sequence, then a random
transaction are generated. Using "start_default_sequence()" method, "default_sequence" can
also be started.
Ovm sequencer has seq_item_export and res_export tlm ports for connecting to ovm driver.
3) Place the ovm_sequencer_utils macro. This macro registers the sequencer for factory
overrides.
`ovm_sequencer_utils(instruction_sequencer)
Sequencer Code;
class instruction_sequencer extends ovm_sequencer #(instruction);
`ovm_sequencer_utils(instruction_sequencer)
endclass
Driver:
ovm_driver is a class which is extended from ovm_componenet. This driver is used in pull
mode. Pull mode means, driver pulls the transaction from the sequencer when it requires.
Ovm driver has 2 TLM ports.
1) Seq_item_port: To get a item from sequencer, driver uses this port. Driver can also send
response back using this port.
2) Rsp_port : This can also be used to send response back to sequencer.
Seq_item_port methods:
Lets implement a driver:
1) Define a driver which takes the instruction from the sequencer and does the processing. In this
example we will just print the instruction type and wait for some delay.
class instruction_driver extends ovm_driver #(instruction);
2) Place the ovm_component_utils macro to define virtual methods like get_type_name and
create.
`ovm_component_utils(instruction_driver)
3) Define Constructor method.
Deriver and sequencer are connected using TLM. Ovm_driver has seq_item_port which is used
to get the transaction from ovm sequencer. This port is connected to ovm_sequencer
seq_item_export Using "<driver>.seq_item_port.connect(<sequencer>.seq_item_export);"
driver and sequencer can be connected. Simillarly "res_port" of driver which is used to send
response from driver to sequencer is connected to "res_export" of the sequencer using
""<driver>.res_port.connect(<sequencer>.res_export);".
Testcase:
This testcase is used only for the demo purpose of this tutorial session. Actually, the sequencer
and the driver and instantiated and their ports are connected in a agent component and used. Lets
implement a testcase
1) Take instances of sequencer and driver and construct both components.
sequencer = new("sequencer", null);
sequencer.build();
driver = new("driver", null);
driver.build();
2)
Connect the seq_item_export to the drivers seq_item_port.
driver.seq_item_port.connect(sequencer.seq_item_export);
3) Using set_confg_string() method, set the default sequence of the sequencer to
"operation_addition". Operation_addition is the sequence which we defined previous.
driver.seq_item_port.connect(sequencer.seq_item_export);
sequencer.print();
fork
begin
run_test();
sequencer.start_default_sequence();
end
#2000 global_stop_request();
join
end
endmodule
Download the example:
ovm_basic_sequence.tar
Browse the code in ovm_basic_sequence.tar
Command to simulate
Your_tool_simulation_command +incdir+path_to_ovm testcase.sv
Log file Output
OVM_INFO @ 0 [RNTST] Running test ...
0: Driving Instruction PUSH_A
10: Driving Instruction PUSH_B
20: Driving Instruction ADD
30: Driving Instruction POP_C
From the above log , we can see that transactions are generates as we defined in ovm sequence.
OVM SEQUENCE 2
Pre Defined Sequences:
Every sequencer in ovm has 3 pre defined sequences. They are
1)Ovm_random_sequence
2)Ovm_exhaustive_sequence.
3)Ovm_simple_sequence
All the user defined sequences which are registered by user and the above three predefined
sequences are stored in sequencer queue.
Ovm_random_sequence :
This sequence randomly selects and executes a sequence from the sequencer sequence library,
excluding ovm_random_sequence itself, and ovm_exhaustive_sequence. From the above image,
from sequence id 2 to till the last sequence, all the sequences are executed randomly. If the
"count" variable of the sequencer is set to 0, then non of the sequence is executed. If the "count"
variable of the sequencer is set to -1, then some random number of sequences from 0 to
"max_random_count" are executed. By default "mac_random_count" is set to 10. "Count" and
"mac_random_count" can be changed using set_config_int().
The sequencer when automatically started executes the sequence which is point by
default_sequence. By default default_sequence variable points to ovm_random_sequence.
ovm_exaustive_sequence:
This sequence randomly selects and executes each sequence from the sequencers sequence
library once in a randc style, excluding itself and ovm_random_sequence.
ovm_simple_sequence:
This sequence simply executes a single sequence item.
In the previous example from OVM_SEQUENCE_1 section.
The print() method of the sequencer in that example printed the following
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
sequencer instruction_sequen+ - sequencer@2
rsp_export ovm_analysis_export - rsp_export@4
seq_item_export ovm_seq_item_pull_+ - seq_item_export@28
default_sequence string 18 operation_addition
count integral 32 -1
max_random_count integral 32 'd10
sequences array 4 -
[0] string 19 ovm_random_sequence
[1] string 23 ovm_exhaustive_sequ+
[2] string 19 ovm_simple_sequence
[3] string 18 operation_addition
max_random_depth integral 32 'd4
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
----------------------------------------------------------------------
Pre_do(), mid_do() and post_do() are callback methods which are in ovm sequence. If user is
interested , he can use these methods. For example, in mid_do() method, user can print the
transaction or the randomized transaction can be fined tuned. These methods should not be
clled by user directly.
Syntax:
Pre_do() is a task , if the method consumes simulation cycles, the behavior may be unexpected.
Example Of Pre_do,Mid_do And Post_do
Lets look at a example: We will define a sequence using `ovm_do macro. This macro has all the
above defined phases.
1)Define the body method using the `ovm_do() macro. Before and after this macro, just call
messages.
virtual task body();
ovm_report_info(get_full_name(),
"Seuqnce Action Macro Phase : Before ovm_do macro ",OVM_LOW);
`ovm_do(req);
ovm_report_info(get_full_name(),
"Seuqnce Action Macro Phase : After ovm_do macro ",OVM_LOW);
endtask
2)Define pre_do() method. Lets just print a message from this method.
3)Define mid_do() method. Lets just print a message from this method.
OVM_INFO@0:reporter[sequencer.demo_ovm_do]
Seuqnce Action Macro Phase : Before ovm_do macro
OVM_INFO@0:reporter[sequencer.demo_ovm_do]
Seuqnce Action Macro Phase : PRE_DO
OVM_INFO@0:reporter[sequencer.demo_ovm_do]
Seuqnce Action Macro Phase : MID_DO
0: Driving Instruction MUL
OVM_INFO@10:reporter[sequencer.demo_ovm_do]
Seuqnce Action Macro Phase : POST_DO
OVM_INFO@10:reporter[sequencer.demo_ovm_do]
Seuqnce Action Macro Phase : After ovm_do macro
The above log file shows the messages from pre_do,mid_do and post_do methods.
List Of Sequence Action Macros:
These macros are used to start sequences and sequence items that were either registered with a
<`ovm-sequence_utils> macro or whose associated sequencer was already set using the
<set_sequencer> method.
`ovm_create(item/sequence)
This action creates the item or sequence using the factory. Only the create phase will be
executed.
`ovm_do(item/sequence)
This macro takes as an argument a ovm_sequence_item variable or sequence . All the above
defined 7 phases will be executed.
`ovm_do_with(item/sequence, Constraint block)
This is the same as `ovm_do except that the constraint block in the 2nd argument is applied to
the item or sequence in a randomize with statement before execution.
`ovm_send(item/sequence)
Create phase and randomize phases are skipped, rest all the phases will be executed. Using
`ovm_create, create phase can be executed. Essentially, an `ovm_do without the create or
randomization.
`ovm_rand_send(item/sequence)
Only create phase is skipped. rest of all the phases will be executed. User should use
`ovm_create to create the sequence or item.
`ovm_rand_send_with(item/sequence , Constraint block)
Only create phase is skipped. rest of all the phases will be executed. User should use
`ovm_create to create the sequence or item. Constraint block will be applied which
randomization.
`ovm_do_pri(item/sequence, priority )
This is the same as `ovm_do except that the sequence item or sequence is executed with the
priority specified in the argument.
`ovm_do_pri_with(item/sequence , constraint block , priority)
This is the same as `ovm_do_pri except that the given constraint block is applied to the item or
sequence in a randomize with statement before execution.
`ovm_send_pri(item/sequence,priority)
This is the same as `ovm_send except that the sequence item or sequence is executed with the
priority specified in the argument.
`ovm_rand_send_pri(item/sequence,priority)
This is the same as `ovm_rand_send except that the sequence item or sequence is executed with
the priority specified in the argument.
`ovm_rand_send_pri_with(item/sequence,priority,constraint block)
This is the same as `ovm_rand_send_pri except that the given constraint block is applied to the
item or sequence in a randomize with statement before execution.
Following macros are used on sequence or sequence items on a different sequencer.
`ovm_create_on(item/sequence,sequencer)
This is the same as `ovm_create except that it also sets the parent sequence to the sequence in
which the macro is invoked, and it sets the sequencer to the specified sequencer argument.
`ovm_do_on(item/sequence,sequencer)
This is the same as `ovm_do except that it also sets the parent sequence to the sequence in which
the macro is invoked, and it sets the sequencer to the specified sequencer argument.
`ovm_do_on_pri(item/sequence,sequencer, priority)
This is the same as `ovm_do_pri except that it also sets the parent sequence to the sequence in
which the macro is invoked, and it sets the sequencer to the specified sequencer argument.
`ovm_do_on_with(item/sequence,sequencer, constraint block)
This is the same as `ovm_do_with except that it also sets the parent sequence to the sequence in
which the macro is invoked, and it sets the sequencer to the specified sequencer argument. The
user must supply brackets around the constraints.
`ovm_do_on_pri_with(item/sequence,sequencer,priority,constraint block)
This is the same as `ovm_do_pri_with except that it also sets the parent sequence to the sequence
in which the macro is invoked, and it sets the sequencer to the specified sequencer argument.
Examples With Sequence Action Macros:
virtual task body();
ovm_report_info(get_full_name(),
"Executing Sequence Action Macro ovm_do",OVM_LOW);
`ovm_do(req)
endtask
virtual task body();
ovm_report_info(get_full_name(),
"Executing Sequence Action Macro ovm_do_with ",OVM_LOW);
`ovm_do_with(req,{ inst == ADD; })
endtask
virtual task body();
ovm_report_info(get_full_name(),
"Executing Sequence Action Macro ovm_create and ovm_send",OVM_LOW);
`ovm_create(req)
req.inst = instruction::PUSH_B;
`ovm_send(req)
endtask
virtual task body();
ovm_report_info(get_full_name(),
"Executing Sequence Action Macro ovm_create and ovm_rand_send",OVM_LOW);
`ovm_create(req)
`ovm_rand_send(req)
endtask
Download the example
ovm_sequence_3.tar
Browse the code in ovm_sequence_3.tar
Command to sun the simulation
Your_tool_simulation_command +incdir+path_to_ovm testcase.sv
Log file report
0: Driving Instruction PUSH_B
OVM_INFO@10:reporter[***]Executing Sequence Action Macro ovm_do_with
10: Driving Instruction ADD
OVM_INFO@20:reporter[***]Executing Sequence Action Macro ovm_create and ovm_send
20: Driving Instruction PUSH_B
OVM_INFO@30:reporter[***]Executing Sequence Action Macro ovm_do
30: Driving Instruction DIV
OVM_INFO@40:reporter[***]Executing Sequence Action Macro ovm_create and
ovm_rand_send
40: Driving Instruction MUL
OVM SEQUENCE 3
Body Callbacks:
Ovm sequences has two callback methods pre_body() and post_body(), which are executed
before and after the sequence body() method execution. These callbacks are called only when
start_sequence() of sequencer or start() method of the sequence is called. User should not call
these methods.
virtual task pre_body()
virtual task post_body()
Example
In this example, I just printed messages from pre_body() and post_body() methods. These
methods can be used for initialization, synchronization with some events or cleanup.
class demo_pre_body_post_body extends ovm_sequence #(instruction);
instruction req;
function new(string name="demo_pre_body_post_body");
super.new(name);
endfunction
`ovm_sequence_utils(demo_pre_body_post_body, instruction_sequencer)
virtual task pre_body();
ovm_report_info(get_full_name()," pre_body() callback ",OVM_LOW);
endtask
virtual task post_body();
ovm_report_info(get_full_name()," post_body() callback ",OVM_LOW);
endtask
virtual task body();
ovm_report_info(get_full_name(),
"body() method: Before ovm_do macro ",OVM_LOW);
`ovm_do(req);
ovm_report_info(get_full_name(),
"body() method: After ovm_do macro ",OVM_LOW);
endtask
endclass
Download the example
ovm_sequence_4.tar
Browse the code in ovm_sequence_4.tar
Command to sun the simulation
Your_tool_simulation_command +incdir+path_to_ovm testcase.sv
Log file report
OVM_INFO @ 0 [RNTST] Running test ...
OVM_INFO @ 0: reporter [***] pre_body() callback
OVM_INFO @ 0: reporter [***] body() method: Before ovm_do macro
0: Driving Instruction SUB
OVM_INFO @ 10: reporter [***] body() method: After ovm_do macro
OVM_INFO @ 10: reporter [***] post_body() callback
Hierarchical Sequences
One main advantage of sequences is smaller sequences can be used to create sequences to
generate stimulus required for todays complex protocol.
To create a sequence using another sequence, following steps has to be done
1)Extend the ovm_sequence class and define a new class.
2)Declare instances of child sequences which will be used to create new sequence.
3)Start the child sequence using <instance>.start() method in body() method.
Sequential Sequences
To executes child sequences sequentially, child sequence start() method should be called
sequentially in body method.
In the below example you can see all the 3 steps mentioned above.
In this example, I have defined 2 child sequences. These child sequences can be used as normal
sequences.
Sequence 1 code:
This sequence generates 4 PUSH_A instructions.
virtual task body();
repeat(4) begin
`ovm_do_with(req, { inst == PUSH_A; });
end
endtask
Sequence 2 code:
This sequence generates 4 PUSH_B instructions.
virtual task body();
repeat(4) begin
`ovm_do_with(req, { inst == PUSH_B; });
end
endtask
Sequential Sequence code:
This sequence first calls sequence 1 and then calls sequence 2.
class sequential_sequence extends ovm_sequence #(instruction);
seq_a s_a;
seq_b s_b;
function new(string name="sequential_sequence");
super.new(name);
endfunction
`ovm_sequence_utils(sequential_sequence, instruction_sequencer)
virtual task body();
`ovm_do(s_a);
`ovm_do(s_b);
endtask
endclass
From the testcase, "sequential_sequence" is selected as "default_sequence".
Download the example
ovm_sequence_5.tar
Browse the code in ovm_sequence_5.tar
Command to sun the simulation
Your_tool_simulation_command +incdir+path_to_ovm testcase.sv
Log file report
To set the arbitaration, use the set_arbitration() method of the sequencer. By default , the
arbitration algorithms is set to SEQ_ARB_FIFO.
function void set_arbitration(SEQ_ARB_TYPE val)
Lets look at a example.
In this example, I have 3 child sequences seq_mul seq_add and seq_sub each of them generates 3
transactions.
Sequence code 1:
virtual task body();
repeat(3) begin
`ovm_do_with(req, { inst == MUL; });
end
endtask
Sequence code 2:
virtual task body();
repeat(3) begin
`ovm_do_with(req, { inst == ADD; });
end
endtask
Sequence code 3:
virtual task body();
repeat(3) begin
`ovm_do_with(req, { inst == SUB; });
end
endtask
Parallel sequence code:
In the body method, before starting child sequences, set the arbitration using set_arbitration(). In
this code, im setting it to SEQ_ARB_RANDOM.
class parallel_sequence extends ovm_sequence #(instruction);
seq_add add;
seq_sub sub;
seq_mul mul;
`ovm_sequence_utils(parallel_sequence, instruction_sequencer)
endclass
ovm_sequence_7.tar
Browse the code in ovm_sequence_7.tar
In the below example, start() method is used to override the default priority value.
Code :
class parallel_sequence extends ovm_sequence #(instruction);
seq_add add;
seq_sub sub;
seq_mul mul;
function new(string name="parallel_sequence");
super.new(name);
endfunction
`ovm_sequence_utils(parallel_sequence, instruction_sequencer)
virtual task body();
m_sequencer.set_arbitration(SEQ_ARB_WEIGHTED);
add = new("add");
sub = new("sub");
mul = new("mul");
fork
sub.start(m_sequencer,this,400);
add.start(m_sequencer,this,300);
mul.start(m_sequencer,this,200);
join
endtask
endclass
Download the example
ovm_sequence_8.tar
Browse the code in ovm_sequence_8.tar
Command to sun the simulation
Your_tool_simulation_command +incdir+path_to_ovm testcase.sv
Log file report
0: Driving Instruction MUL
10: Driving Instruction ADD
20: Driving Instruction SUB
30: Driving Instruction SUB
40: Driving Instruction ADD
50: Driving Instruction ADD
60: Driving Instruction ADD
70: Driving Instruction MUL
80: Driving Instruction SUB
OVM SEQUENCE 5
Sequencer Registration Macros
Sequence Registration Macros does the following
1) Implements get_type_name method.
2) Implements create() method.
3) Registers with the factory.
4) Implements the static get_type() method.
5) Implements the virtual get_object_type() method.
6) Registers the sequence type with the sequencer type.
7) Defines p_sequencer variable. p_sequencer is a handle to its sequencer.
8) Implements m_set_p_sequencer() method.
If there are no local variables, then use following macro
`ovm_sequence_utils(TYPE_NAME,SQR_TYPE_NAME)
If there are local variables in sequence, then use macro
`ovm_sequence_utils_begin(TYPE_NAME,SQR_TYPE_NAME)
`ovm_field_* macro invocations here
`ovm_sequence_utils_end
Macros `ovm_field_* are used for define utility methods.
These `ovm_field_* macros are discussed in
OVM_TRANSACTION
Example to demonstrate the usage of the above macros:
class seq_mul extends ovm_sequence #(instruction);
rand integer num_inst ;
instruction req;
constraint num_c { num_inst inside { 3,5,7 }; };
`ovm_sequence_utils_begin(seq_mul,instruction_sequencer)
`ovm_field_int(num_inst, OVM_ALL_ON)
`ovm_sequence_utils_end
function new(string name="seq_mul");
super.new(name);
endfunction
virtual task body();
ovm_report_info(get_full_name(),
$psprintf("Num of transactions %d",num_inst),OVM_LOW);
repeat(num_inst) begin
`ovm_do_with(req, { inst == MUL; });
end
endtask
endclass
Download the example
ovm_sequence_9.tar
Browse the code in ovm_sequence_9.tar
"*abc" -All the lower level components which ends with "abc".
Example: "xabc","xyabc","xyzabc" ....
"abc*" -All the lower level components which starts with "abc".
Example: "abcx","abcxy","abcxyz" ....
"ab?" -All the lower level components which start with "ab" , then followed by one more
character.
Example: "abc","abb","abx" ....
"?bc" -All the lower level components which start with any one character ,then followed by
"c".
Example: "abc","xbc","bbc" ....
"a?c" -All the lower level components which start with "a" , then followed by one more
character and then followed by "c".
Example: "abc","aac","axc" …..
There are two ways to get the configuration data:
1)Automatic : Using Field macros
2)Manual : using gte_config_* methods.
Automatic Configuration:
To use the atomic configuration, all the configurable fields should be defined using ovm
component field macros and ovm component utilities macros.
OVM_TRANSACTION
Example:
Following example is from link
OVM_TESTBENCH
2 Configurable fields, a integer and a string are defined in env, agent, monitor and driver classes.
Topology of the environment using these classes is
Driver class Source Code:
Similar to driver class, all other components env, agent and monitor are define.
class driver extends ovm_driver;
integer int_cfg;
string str_cfg;
`ovm_component_utils_begin(driver)
`ovm_field_int(int_cfg, OVM_DEFAULT)
`ovm_field_string(str_cfg, OVM_DEFAULT)
`ovm_component_utils_end
function new(string name, ovm_component parent);
super.new(name, parent);
endfunction
function void build();
super.build();
endfunction
endclass
Testcase:
Using set_config_int() and set_config_string() configure variables at various hierarchal
locations.
//t_env.ag1.drv.int_cfg
//t_env.ag1.mon.int_cfg
set_config_int("*.ag1.*","int_cfg",32);
//t_env.ag2.drv
set_config_int("t_env.ag2.drv","int_cfg",32);
//t_env.ag2.mon
set_config_int("t_env.ag2.mon","int_cfg",32);
//t_env.ag1.mon.str_cfg
//t_env.ag2.mon.str_cfg
//t_env.ag1.drv.str_cfg
//t_env.ag2.drv.str_cfg
set_config_string("*.ag?.*","str_cfg","pars");
//t_env.str_cfg
set_config_string("t_env","str_cfg","abcd");
Log file
When print_config_settings method is called
ovm_test_top.t_env.ag1.drv
ovm_test_top.*.ag1.* int_cfg int 32
ovm_test_top.t_env.ag1.drv.rsp_port
ovm_test_top.*.ag?.* str_cfg string pars
ovm_test_top.t_env.ag1.drv.rsp_port
ovm_test_top.*.ag1.* int_cfg int 32
ovm_test_top.t_env.ag1.drv.sqr_pull_port
ovm_test_top.*.ag?.* str_cfg string pars
ovm_test_top.t_env.ag1.drv.sqr_pull_port
ovm_test_top.*.ag1.* int_cfg int 32
When print_config_matches is set to 1.
OVM_INFO @ 0: ovm_test_top.t_env [auto-configuration]
Auto-configuration matches for component ovm_test_top.t_env (env).
Last entry for a given field takes precedence.
SPECIFICATION
Switch Specification:
This is a simple switch. Switch is a packet based protocol. Switch drives the incoming packet
which comes from the input port to output ports based on the address contained in the packet.
The switch has a one input port from which the packet enters. It has four output ports where the
packet is driven out.
Packet Format:
Packet contains 3 parts. They are Header, data and frame check sequence.
Packet width is 8 bits and the length of the packet can be between 4 bytes to 259 bytes.
Packet Header:
Packet header contains three fields DA, SA and length.
DA: Destination address of the packet is of 8 bits. The switch drives the packet to respective
ports based on this destination address of the packets. Each output port has 8-bit unique port
address. If the destination address of the packet matches the port address, then switch drives the
packet to the output port.
Input Port
Packets are sent into the switch using input port.
All the signals are active high and are synchronous to the positive edge of clock signal.
input port has 2 input signals. They are
input [7:0] data;
input data_status;
To send the packet in to switch,
1. Assert the data_status signal.
2. Send the packet on the data signal byte by byte.
3. After sending all the data bytes, deassert the data_status signal.
4. There should be at least 3 clock cycles difference between packets.
Output Port
Switch sends the packets out using the output ports. There are 4 ports, each having data, ready
and read signals. All the signals are active high and are synchronous to the positive edge of
clock signal.
Signal list is
output [7:0] port0;
output [7:0] port1;
output [7:0] port2;
output [7:0] port3;
output ready_0;
output ready_1;
output ready_2;
output ready_3;
input read_0;
input read_1;
input read_2;
input read_3;
When the data is ready to be sent out from the port, switch asserts ready_* signal high indicating
that data is ready to be sent.
If the read_* signal is asserted, when ready_* is high, then the data comes out of the port_*
signal after one clock cycle.
RTL code:
RTL code is attached with the tar files. From the Phase 1, you can download the tar files.
VERIFICATION PLAN
Overview
This Document describes the Verification Plan for Switch. The Verification Plan is based on
System Verilog Hardware Verification Language. The methodology used for Verification is
Constraint random coverage driven verification.
Feature Extraction
This section contains list of all the features to be verified.
1)
ID: Configuration
Description: Configure all the 4 port address with unique values.
2)
ID: Packet DA
Description: DA field of packet should be any of the port address. All the 4 port address should
be used.
3)
ID : Packet payload
Description: Length can be from 0 to 255. Send packets with all the lengths.
4)
ID: Length
Description:
Length field contains length of the payload.
Send Packet with correct length field and incorrect length fields.
5)
ID: FCS
Description:
Good FCS: Send packet with good FCS.
Bad FCS: Send packet with corrupted FCS.
Stimulus Generation Plan
1) Packet DA: Generate packet DA with the configured address.
2) Payload length: generate payload length ranging from 2 to 255.
3) Correct or Incorrect Length field.
4) Generate good and bad FCS.
Coverage Plan
1) Cover all the port address configurations.
2) Cover all the packet lengths.
3) Cover all correct and incorrect length fields.
4) Cover good and bad FCS.
5) Cover all the above combinations.
Verification Environment
PHASE 1 TOP
In phase 1,
1) We will write SystemVerilog Interfaces for input port, output port and memory port.
2) We will write Top module where testcase and DUT instances are done.
3) DUT and TestBench interfaces are connected in top module.
4) Clock is generator in top module.
NOTE: In every file you will see the syntax
`ifndef GUARD_*
`endif GUARD_*
Interfaces
In the interface.sv file, declare the 3 interfaces in the following way.
All the interfaces has clock as input.
All the signals in interface are logic type.
All the signals are synchronized to clock except reset in clocking block.
Signal directional w.r.t TestBench is specified with modport.
`ifndef GUARD_INTERFACE
`define GUARD_INTERFACE
//////////////////////////////////////////
// Interface declaration for the memory///
//////////////////////////////////////////
interface mem_interface(input bit clock);
logic [7:0] mem_data;
logic [1:0] mem_add;
logic mem_en;
logic mem_rd_wr;
////////////////////////////////////////////
// Interface for the input side of switch.//
// Reset signal is also passed hear. //
////////////////////////////////////////////
interface input_interface(input bit clock);
logic data_status;
logic [7:0] data_in;
logic reset;
endinterface
/////////////////////////////////////////////////
// Interface for the output side of the switch.//
// output_interface is for only one output port//
/////////////////////////////////////////////////
//////////////////////////////////////////////////
`endif
Testcase
Testcase is a program block which provides an entry point for the test and creates a scope that
encapsulates program-wide data. Currently this is an empty testcase which just ends the
simulation after 100 time units. Program block contains all the above declared interfaces as
arguments. This testcase has initial and final blocks.
`ifndef GUARD_TESTCASE
`define GUARD_TESTCASE
program testcase(mem_interface.MEM mem_intf,input_interface.IP input_intf,output_interface.
OP output_intf[4]);
initial
begin
$display(" ******************* Start of testcase ****************");
#1000;
end
final
$display(" ******************** End of testcase *****************");
endprogram
`endif
Top Module
The modules that are included in the source text but are not instantiated are called top modules.
This module is the highest scope of modules. Generally this module is named as "top" and
referenced as "top module". Module name can be anything.
Do the following in the top module:
1)Generate the clock signal.
bit Clock;
initial
forever #10 Clock = ~Clock;
2)Do the instances of memory interface.
mem_interface mem_intf(Clock);
3)Do the instances of input interface.
input_interface input_intf(Clock);
4)There are 4 output ports. So do 4 instances of output_interface.
output_interface output_intf[4](Clock);
5)Do the instance of testcase and pass all the above declared interfaces.
testcase TC (mem_intf,input_intf,output_intf);
6)Do the instance of DUT.
switch DUT (.
7)Connect all the interfaces and DUT. The design which we have taken is in verilog. So Verilog
DUT instance is connected signal by signal.
switch DUT (.clk(Clock),
.reset(input_intf.reset),
.data_status(input_intf.data_status),
.data(input_intf.data_in),
.port0(output_intf[0].data_out),
.port1(output_intf[1].data_out),
.port2(output_intf[2].data_out),
.port3(output_intf[3].data_out),
.ready_0(output_intf[0].ready),
.ready_1(output_intf[1].ready),
.ready_2(output_intf[2].ready),
.ready_3(output_intf[3].ready),
.read_0(output_intf[0].read),
.read_1(output_intf[1].read),
.read_2(output_intf[2].read),
.read_3(output_intf[3].read),
.mem_en(mem_intf.mem_en),
.mem_rd_wr(mem_intf.mem_rd_wr),
.mem_add(mem_intf.mem_add),
.mem_data(mem_intf.mem_data));
Top Module Source Code:
`ifndef GUARD_TOP
`define GUARD_TOP
module top();
/////////////////////////////////////////////////////
// Clock Declaration and Generation //
/////////////////////////////////////////////////////
bit Clock;
initial
forever #10 Clock = ~Clock;
/////////////////////////////////////////////////////
// Memory interface instance //
/////////////////////////////////////////////////////
mem_interface mem_intf(Clock);
/////////////////////////////////////////////////////
// Input interface instance //
/////////////////////////////////////////////////////
input_interface input_intf(Clock);
/////////////////////////////////////////////////////
// output interface instance //
/////////////////////////////////////////////////////
output_interface output_intf[4](Clock);
/////////////////////////////////////////////////////
// Program block Testcase instance //
/////////////////////////////////////////////////////
testcase TC (mem_intf,input_intf,output_intf);
/////////////////////////////////////////////////////
// DUT instance and signal connection //
/////////////////////////////////////////////////////
PHASE 2 ENVIRONMENT
In this phase, we will write
Environment class.
Virtual interface declaration.
Defining Environment class constructor.
Defining required methods for execution. Currently these methods will not be implemented in
this phase.
All the above are done in Environment.sv file.
We will write a testcase using the above define environment class in testcase.sv file.
Environment Class:
The class is a base class used to implement verification environments. Testcase contains the
instance of the environment class and has access to all the public declaration of environment
class.
All methods are declared as virtual methods. In environment class, we will formalize the
simulation steps using virtual methods. The methods are used to control the execution of the
simulation.
Following are the methods which are going to be defined in environment class.
1) new() : In constructor method, we will connect the virtual interfaces which are passed as
argument to the virtual interfaces to those which are declared in environment class.
2) build(): In this method , all the objects like driver, monitor etc are constructed. Currently this
method is empty as we did not develop any other component.
3) reset(): in this method we will reset the DUT.
4) cfg_dut(): In this method, we will configure the DUT output port address.
5) start(): in this method, we will call the methods which are declared in the other components
like driver and monitor.
6) wait_for_end(): this method is used to wait for the end of the simulation. Waits until all the
required operations in other components are done.
7) report(): This method is used to print the TestPass and TestFail status of the simulation, based
on the error count..
8) run(): This method calls all the above declared methods in a sequence order. The testcase calls
this method, to start the simulation.
We are not implementing build(), reset(), cfg_dut() , strat() and report() methods in this phase.
Connecting the virtual interfaces of Environment class to the physical interfaces of top module.
Verification environment contains the declarations of the virtual interfaces. Virtual interfaces are
just a handles(like pointers). When a virtual interface is declared, it only creats a handle. It
doesnot creat a real interface.
Constructor method should be declared with virtual interface as arguments, so that when the
object is created in testcase, new() method can pass the interfaces in to environment class where
they are assigned to the local virtual interface handle. With this, the Environment class virtual
interfaces are pointed to the physical interfaces which are declared in the top module.
In constructor methods, the interfaces which are arguments are connected to the virtual interfaces
of environment class.
this.mem_intf = mem_intf_new ;
this.input_intf = input_intf_new ;
this.output_intf = output_intf_new ;
Run :
The run() method is called from the testcase to start the simulation. run() method calls all the
methods which are defined in the Environment class.
task run();
$display(" %0d : Environment : start of run() method",$time);
build();
reset();
cfg_dut();
start();
wait_for_end();
report();
$display(" %0d : Environment : end of run() method",$time);
endtask : run
Environment Class Source Code:
`ifndef GUARD_ENV
`define GUARD_ENV
class Environment ;
task cfg_dut();
$display(" %0d : Environment : start of cfg_dut() method",$time);
$display(" %0d : Environment : end of cfg_dut() method",$time);
endtask : cfg_dut
task start();
$display(" %0d : Environment : start of start() method",$time);
$display(" %0d : Environment : end of start() method",$time);
endtask : start
task wait_for_end();
$display(" %0d : Environment : start of wait_for_end() method",$time);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end
task run();
$display(" %0d : Environment : start of run() method",$time);
build();
reset();
cfg_dut();
start();
wait_for_end();
report();
$display(" %0d : Environment : end of run() method",$time);
endtask : run
task report();
endtask : report
endclass
`endif
We will create a file Global.sv for global requirement. In this file, define all the port address as
macros in this file. Define a variable error as integer to keep track the number of errors occurred
during the simulation.
`ifndef GUARD_GLOBALS
`define GUARD_GLOBALS
`define P0 8'h00
`define P1 8'h11
`define P2 8'h22
`define P3 8'h33
int error = 0;
int num_of_pkts = 10;
`endif
Now we will update the testcase. Take an instance of the Environment class and call the run
method of the Environment class.
`ifndef GUARD_TESTCASE
`define GUARD_TESTCASE
program testcase(mem_interface.MEM mem_intf,input_interface.IP
input_intf,output_interface.OP output_intf[4]);
Environment env;
initial
begin
$display(" ******************* Start of testcase ****************");
env = new(mem_intf,input_intf,output_intf);
env.run();
#1000;
end
final
$display(" ******************** End of testcase *****************");
endprogram
`endif
Download the phase 2 source code:
switch_2.tar
Browse the code in switch_2.tar
Run the simulation:
vcs -sverilog -f filelist -R -ntb_opts dtm
Log report after the simulation:
PHASE 3 RESET
In this phase we will reset and configure the DUT.
The Environment class has reset() method which contains the logic to reset the DUT and
cfg_dut() method which contains the logic to configure the DUT port address.
NOTE: Clocking block signals can be driven only using a non-blocking assignment.
Define the reset() method.
1) Set all the DUT input signals to a known state.
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 0;
2) Reset the DUT.
// Reset the DUT
input_intf.reset <= 1;
repeat (4) @ input_intf.clock;
input_intf.reset <= 0;
3) Updated the cfg_dut method.
task cfg_dut();
$display(" %0d : Environment : start of cfg_dut() method",$time);
mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
task wait_for_end();
$display(" %0d : Environment : start of wait_for_end() method",$time);
repeat(10000) @(input_intf.clock);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end
switch_3.tar
Browse the code in switch_3.tar
4) Declare the packet field as rand. All fields are bit data types. All fields are 8 bit packet array.
Declare the payload as dynamic array.
rand bit [7:0] length;
rand bit [7:0] da;
rand bit [7:0] sa;
rand byte data[];//Payload using Dynamic array,size is generated on the fly
rand byte fcs;
5) Constraint the DA field to be any one of the configured address.
constraint address_c { da inside {`P0,`P1,`P2,`P3} ; }
6) Constrain the payload dynamic array size to between 1 to 255.
constraint payload_size_c { data.size inside { [1 : 255]};}
7) Constrain the payload length to the length field based on the length type.
constraint length_kind_c {
(length_kind == GOOD_LENGTH) -> length == data.size;
(length_kind == BAD_LENGTH) -> length == data.size + 2 ; }
Use solve before to direct the randomization to generate first the payload dynamic array size and
then randomize length field.
constraint solve_size_length { solve data.size before length; }
8) Constrain the FCS field initial value based on the fcs kind field.
constraint fcs_kind_c {
(fcs_kind == GOOD_FCS) -> fcs == 8'b0;
(fcs_kind == BAD_FCS) -> fcs == 8'b1; }
9) Define the FCS method.
function byte cal_fcs;
integer i;
byte result ;
result = 0;
result = result ^ da;
result = result ^ sa;
result = result ^ length;
for (i = 0;i< data.size;i++)
result = result ^ data[i];
result = fcs ^ result;
return result;
endfunction : cal_fcs
10) Define display methods:
Display method displays the current value of the packet fields to standard output.
virtual function void display();
$display("\n---------------------- PACKET KIND ------------------------- ");
$display(" fcs_kind : %s ",fcs_kind.name() );
$display(" length_kind : %s ",length_kind.name() );
$display("-------- PACKET ---------- ");
$display(" 0 : %h ",da);
$display(" 1 : %h ",sa);
$display(" 2 : %h ",length);
foreach(data[i])
$write("%3d : %0h ",i + 3,data[i]);
$display("\n %2d : %h ",data.size() + 3 , cal_fcs);
$display("----------------------------------------------------------- \n");
endfunction : display
11) Define pack method:
Packing is commonly used to convert the high level data to low level data that can be applied to
DUT. In packet class various fields are generated. Required fields are concatenated to form a
stream of bytes which can be driven conveniently to DUT interface by the driver.
`ifndef GUARD_PACKET
`define GUARD_PACKET
class packet;
rand fcs_kind_t fcs_kind;
rand length_kind_t length_kind;
constraint length_kind_c {
(length_kind == GOOD_LENGTH) -> length == data.size;
(length_kind == BAD_LENGTH) -> length == data.size + 2 ; }
constraint fcs_kind_c {
(fcs_kind == GOOD_FCS) -> fcs == 8'b0;
(fcs_kind == BAD_FCS) -> fcs == 8'b1; }
endclass
Now we will write a small program to test our packet implantation. This program block is not
used to verify the DUT.
Write a simple program block and do the instance of packet class. Randomize the packet and call
the display method to analyze the generation. Then pack the packet in to bytes and then unpack
bytes and then call compare method to check all the methods.
Program Block Source Code
program test;
end
else
$display(" *** Randomization Failed ***");
endprogram
switch_4.tar
Browse the code in switch_4.tar
Randomization Sucessesfull.
---------------------- PACKET KIND -------------------------
fcs_kind : BAD_FCS
length_kind : GOOD_LENGTH
-------- PACKET ----------
0 : 00
1 : f7
2 : be
3 : a6 4 : 1b 5 : b5 6 : fa 7 : 4e 8 : 15 9 : 7d 10 : 72 11 : 96 12 : 31 13 : c4 14 : aa 15 :
c4 16 : cf 17 : 4f 18 : f4 19 : 17 20 : 88 21 : f1 22 : 2c 23 : ce 24 : 5 25 : cb 26 : 8c 27 :
1a 28 : 37 29 : 60 30 : 5f 31 : 7a 32 : a2 33 : f0 34 : c9 35 : dc 36 : 41 37 : 3f 38 : 12 39 :
f4 40 : df 41 : c5 42 : d7 43 : 94 44 : 88 45 : 1 46 : 31 47 : 29 48 : d6 49 : f4 50 : d9 51 :
4f 52 : 0 53 : dd 54 : d2 55 : a6 56 : 59 57 : 43 58 : 45 59 : f2 60 : a2 61 : a1 62 : fd 63 :
ea 64 : c1 65 : 20 66 : c7 67 : 20 68 : e1 69 : 97 70 : c6 71 : cf 72 : cd 73 : 17 74 : 99 75 :
49 76 : b8 77 : 1c 78 : df 79 : e6 80 : 1a 81 : ce 82 : 8c 83 : ec 84 : b6 85 : bb 86 : a5 87 :
17 88 : cb 89 : 32 90 : e1 91 : 83 92 : 96 93 : e 94 : ee 95 : 57 96 : 33 97 : cd 98 : 62 99 :
88 100 : 7b 101 : e6 102 : 41 103 : ad 104 : 26 105 : ee 106 : 9c 107 : 95 108 : a7 109 : b8 110 :
83 111 : f 112 : ca 113 : ec 114 : b5 115 : 8d 116 : d8 117 : 2f 118 : 6f 119 : ea 120 : 4c 121 : 35
122 : 41 123 : f2 124 : 4e 125 : 89 126 : d8 127 : 78 128 : f1 129 : d 130 : d6 131 : d5 132 : 8 133
: c 134 : de 135 : a9 136 : 1d 137 : a0 138 : ae 139 : 99 140 : f5 141 : 53 142 : d8 143 : 7a 144 :
4c 145 : d4 146 : b8 147 : 54 148 : b7 149 : c3 150 : c9 151 : 7b 152 : a3 153 : 71 154 : 2b 155 :
b4 156 : 50 157 : 54 158 : 22 159 : 95 160 : df 161 : 17 162 : c9 163 : 41 164 : 80 165 : 2b 166 :
f0 167 : ba 168 : 4a 169 : a9 170 : 7f 171 : 13 172 : 1e 173 : 12 174 : a8 175 : 2 176 : 3 177 : 3d
178 : 71 179 : e6 180 : 96 181 : 89 182 : c6 183 : 46 184 : d6 185 : 1b 186 : 5f 187 : 20 188 : a0
189 : a3 190 : 49 191 : 79 192 : 9
193 : 53
-----------------------------------------------------------
..............
..............
..............
PHASE 5 DRIVER
In phase 5 we will write a driver and then instantiate the driver in environment and send packet
in to DUT. Driver class is defined in Driver.sv file.
Driver is class which generates the packets and then drives it to the DUT input interface and
pushes the packet in to mailbox.
1) Declare a packet.
packet gpkt;
2) Declare a virtual input_interface of the switch. We will connect this to the Physical interface
of the top module same as what we did in environment class.
3) Define a mailbox "drvr2sb" which is used to send the packets to the score board.
mailbox drvr2sb;
4) Define new constructor with arguments, virtual input interface and a mail box which is used to
send packets from the driver to scoreboard.
gpkt = new();
repeat($root.num_of_pkts)
if ( pkt.randomize())
begin
$display (" %0d : Driver : Randomization Successes full.",$time);
...........
...........
else
begin
$display (" %0d Driver : ** Randomization failed. **",$time);
............
...........
pkt.display();
length = pkt.byte_pack(bytes);
Then send the packet byte in to the switch by asserting data_status of the input interface signal
and driving the data bytes on to the data_in signal.
foreach(bytes[i])
begin
@(posedge input_intf.clock);
input_intf.cb.data_status <= 1;
input_intf.cb.data_in <= bytes[i];
end
After driving all the data bytes, deassert data_status signal of the input interface.
@(posedge input_intf.clock);
input_intf.cb.data_status <= 0;
input_intf.cb.data_in <= 0;
drvr2sb.put(pkt);
If randomization fails, increment the error counter which is defined in Globals.sv file
$root.error++;
Driver Class Source Code:
`ifndef GUARD_DRIVER
`define GUARD_DRIVER
class Driver;
virtual input_interface.IP input_intf;
mailbox drvr2sb;
packet gpkt;
//// constructor method ////
function new(virtual input_interface.IP input_intf_new,mailbox drvr2sb);
this.input_intf = input_intf_new ;
if(drvr2sb == null)
begin
$display(" **ERROR**: drvr2sb is null");
$finish;
end
else
this.drvr2sb = drvr2sb;
gpkt = new();
endfunction : new
/// method to send the packet to DUT ////////
task start();
packet pkt;
int length;
logic [7:0] bytes[];
repeat($root.num_of_pkts)
begin
repeat(3) @(posedge input_intf.clock);
pkt = new gpkt;
//// Randomize the packet /////
if ( pkt.randomize())
begin
$display (" %0d : Driver : Randomization Successes full. ",$time);
//// display the packet content ///////
pkt.display();
///// assert the data_status signal and send the packed bytes //////
foreach(bytes[i])
begin
@(posedge input_intf.clock);
input_intf.cb.data_status <= 1;
input_intf.cb.data_in <= bytes[i];
end
$display(" %0d : Driver : Finished Driving the packet with length %0d",$time,length);
end
else
begin
$display (" %0d Driver : ** Randomization failed. **",$time);
////// Increment the error count in randomization fails ////////
$root.error++;
end
end
endtask : start
endclass
`endif
Now we will take the instance of the driver in the environment class.
1) Declare a mailbox "drvr2sb" which will be used to connect the scoreboard and driver.
mailbox drvr2sb;
2) Declare a driver object "drvr".
Driver drvr;
3) In build method, construct the mail box.
drvr2sb = new();
4) In build method, construct the driver object. Pass the input_intf and "drvr2sb" mail box.
drvr= new(input_intf,drvr2sb);
5) To start sending the packets to the DUT, call the start method of "drvr" in the start method of
Environment class.
drvr.start();
Environment Class Source Code:
`ifndef GUARD_ENV
`define GUARD_ENV
class Environment ;
virtual mem_interface.MEM mem_intf ;
virtual input_interface.IP input_intf ;
virtual output_interface.OP output_intf[4] ;
Driver drvr;
mailbox drvr2sb;
function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );
this.mem_intf = mem_intf_new ;
this.input_intf = input_intf_new ;
this.output_intf = output_intf_new ;
drvr2sb = new();
drvr= new(input_intf,drvr2sb);
task reset();
$display(" %0d : Environment : start of reset() method",$time);
// Drive all DUT inputs to a known state
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 0;
task cfg_dut();
$display(" %0d : Environment : start of cfg_dut() method",$time);
mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
task start();
$display(" %0d : Environment : start of start() method",$time);
drvr.start();
task wait_for_end();
$display(" %0d : Environment : start of wait_for_end() method",$time);
repeat(10000) @(input_intf.clock);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end
task run();
$display(" %0d : Environment : start of run() method",$time);
build();
reset();
cfg_dut();
start();
wait_for_end();
report();
$display(" %0d : Environment : end of run() method",$time);
endtask : run
switch_5.tar
Browse the code in switch_5.tar
..................
..................
..................
PHASE 6 RECEIVER
In this phase, we will write a receiver and use the receiver in environment class to collect the
packets coming from the switch output_interface.
Receiver collects the data bytes from the interface signal. And then unpacks the bytes in to
packet and pushes it into mailbox.
Receiver class is written in reveicer.sv file.
1) Declare a virtual output_interface. We will connect this to the Physical interface of the top
module, same as what we did in environment class.
virtual output_interface.OP output_intf;
2) Declare a mailbox "rcvr2sb" which is used to send the packets to the score board
mailbox rcvr2sb;
3) Define new constructor with arguments, virtual input interface and a mail box which is used to
send packets from the receiver to scoreboard.
task start();
logic [7:0] bytes[];
packet pkt;
forever
begin
repeat(2) @(posedge output_intf.clock);
wait(output_intf.cb.ready)
output_intf.cb.read <= 1;
repeat(2) @(posedge output_intf.clock);
while (output_intf.cb.ready)
begin
bytes = new[bytes.size + 1](bytes);
bytes[bytes.size - 1] = output_intf.cb.data_out;
@(posedge output_intf.clock);
end
output_intf.cb.read <= 0;
@(posedge output_intf.clock);
$display(" %0d : Receiver : Received a packet of length %0d",$time,bytes.size);
pkt = new();
pkt.byte_unpack(bytes);
pkt.display();
rcvr2sb.put(pkt);
bytes.delete();
end
endtask : start
endclass
`endif
Now we will take the instance of the receiver in the environment class.
1) Declare a mailbox "rcvr2sb" which will be used to connect the scoreboard and receiver.
mailbox rcvr2sb;
Receiver rcvr[4];
rcvr2sb = new();
4) In build method, construct the receiver object. Pass the output_intf and "rcvr2sb" mail box.
There are 4 output interfaces and receiver objects. We will connect one receiver for one output
interface.
foreach(rcvr[i])
rcvr[i]= new(output_intf[i],rcvr2sb);
5) To start collecting the packets from the DUT, call the "start" method of "rcvr" in the "start"
method of Environment class.
task start();
$display(" %0d : Environment : start of start() method",$time);
fork
drvr.start();
rcvr[0].start();
rcvr[1].start();
rcvr[2].start();
rcvr[3].start();
join_any
$display(" %0d : Environment : end of start() method",$time);
endtask : start
Driver drvr;
Receiver rcvr[4];
mailbox drvr2sb;
mailbox rcvr2sb;
function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );
this.mem_intf = mem_intf_new ;
this.input_intf = input_intf_new ;
this.output_intf = output_intf_new ;
rcvr2sb = new();
drvr= new(input_intf,drvr2sb);
foreach(rcvr[i])
rcvr[i]= new(output_intf[i],rcvr2sb);
task reset();
$display(" %0d : Environment : start of reset() method",$time);
// Drive all DUT inputs to a known state
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 0;
// Reset the DUT
input_intf.reset <= 1;
repeat (4) @ input_intf.clock;
input_intf.reset <= 0;
task cfg_dut();
$display(" %0d : Environment : start of cfg_dut() method",$time);
mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
$display(" %0d : Environment : end of cfg_dut() method",$time);
endtask :cfg_dut
task start();
$display(" %0d : Environment : start of start() method",$time);
fork
drvr.start();
rcvr[0].start();
rcvr[1].start();
rcvr[2].start();
rcvr[3].start();
join_any
$display(" %0d : Environment : end of start() method",$time);
endtask : start
task wait_for_end();
$display(" %0d : Environment : start of wait_for_end() method",$time);
repeat(10000) @(input_intf.clock);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end
task run();
$display(" %0d : Environment : start of run() method",$time);
build();
reset();
cfg_dut();
start();
wait_for_end();
report();
$display(" %0d : Environment : end of run() method",$time);
endtask : run
task report();
endtask: report
endclass
`endif
Download the phase 6 source code:
switch_6.tar
Browse the code in switch_6.tar
Run the command:
vcs -sverilog -f filelist -R -ntb_opts dtm
PHASE 7 SCOREBOARD
In this phase we will see the scoreboard implementation.
Scoreboard has 2 mailboxes. One is used to for getting the packets from the driver and other
from the receiver. Then the packets are compared and if they don't match, then error is asserted.
Scoreboard in implemented in file Scoreboard.sv.
mailbox drvr2sb;
mailbox rcvr2sb;
3) Connect the mailboxes of the constructor to the mail boxes of the scoreboard.
this.drvr2sb = drvr2sb;
this.rcvr2sb = rcvr2sb;
rcvr2sb.get(pkt_rcv);
$display(" %0d : Scorebooard : Scoreboard received a packet from receiver ",$time);
drvr2sb.get(pkt_exp);
Compare both packets and increment an error counter if they are not equal.
if(pkt_rcv.compare(pkt_exp))
$display(" %0d : Scoreboardd :Packet Matched ",$time);
else
$root.error++;
`ifndef GUARD_SCOREBOARD
`define GUARD_SCOREBOARD
class Scoreboard;
mailbox drvr2sb;
mailbox rcvr2sb;
task start();
packet pkt_rcv,pkt_exp;
forever
begin
rcvr2sb.get(pkt_rcv);
$display(" %0d : Scorebooard : Scoreboard received a packet from receiver ",$time);
drvr2sb.get(pkt_exp);
if(pkt_rcv.compare(pkt_exp))
$display(" %0d : Scoreboardd :Packet Matched ",$time);
else
$root.error++;
end
endtask : start
endclass
`endif
Now we will see how to connect the scoreboard in the Environment class.
1) Declare a scoreboard.
Scoreboard sb;
2) Construct the scoreboard in the build method. Pass the drvr2sb and rcvr2sb mailboxes to the
score board constructor.
sb = new(drvr2sb,rcvr2sb);
sb.start();
task report();
$display("\n\n*************************************************");
if( 0 == $root.error)
$display("******** TEST PASSED *********");
else
$display("******** TEST Failed with %0d errors *********",$root.error);
$display("*************************************************\n\n");
endtask : report
`ifndef GUARD_ENV
`define GUARD_ENV
class Environment ;
virtual mem_interface.MEM mem_intf ;
virtual input_interface.IP input_intf ;
virtual output_interface.OP output_intf[4] ;
Driver drvr;
Receiver rcvr[4];
Scoreboard sb;
mailbox drvr2sb ;
mailbox rcvr2sb ;
this.mem_intf = mem_intf_new ;
this.input_intf = input_intf_new ;
this.output_intf = output_intf_new ;
sb = new(drvr2sb,rcvr2sb);
drvr= new(input_intf,drvr2sb);
foreach(rcvr[i])
rcvr[i]= new(output_intf[i],rcvr2sb);
$display(" %0d : Environment : end of build() method",$time);
endfunction : build
task reset();
$display(" %0d : Environment : start of reset() method",$time);
// Drive all DUT inputs to a known state
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 0;
task cfg_dut();
$display(" %0d : Environment : start of cfg_dut() method",$time);
mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
task start();
$display(" %0d : Environment : start of start() method",$time);
fork
drvr.start();
rcvr[0].start();
rcvr[1].start();
rcvr[2].start();
rcvr[3].start();
sb.start();
join_any
$display(" %0d : Environment : end of start() method",$time);
endtask : start
task wait_for_end();
$display(" %0d : Environment : start of wait_for_end() method",$time);
repeat(10000) @(input_intf.clock);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end
task run();
$display(" %0d : Environment : start of run() method",$time);
build();
reset();
cfg_dut();
start();
wait_for_end();
report();
$display(" %0d : Environment : end of run() method",$time);
endtask: run
task report();
$display("\n\n*************************************************");
if( 0 == $root.error)
$display("******** TEST PASSED *********");
else
$display("******** TEST Failed with %0d errors *********",$root.error);
$display("*************************************************\n\n");
endtask : report
endclass
`endif
da : coverpoint pkt.da {
bins p0 = { `P0 };
bins p1 = { `P1 };
bins p2 = { `P2 };
bins p3 = { `P3 }; }
function new();
switch_coverage = new();
endfunction : new
3) Write task which calls the sample method to cover the points.
`ifndef GUARD_COVERAGE
`define GUARD_COVERAGE
class coverage;
packet pkt;
covergroup switch_coverage;
function new();
switch_coverage = new();
endfunction : new
endclass
`endif
Now we will use this coverage class instance in scoreboard.
1) Take an instance of coverage class and construct it in scoreboard class.
coverage cov = new();
2) Call the sample method and pass the exp_pkt to the sample method.
cov.sample(pkt_exp);
Source Code Of The Scoreboard Class:
`ifndef GUARD_SCOREBOARD
`define GUARD_SCOREBOARD
class Scoreboard;
mailbox drvr2sb;
mailbox rcvr2sb;
endclass
`endif
Download the phase 8 score code:
switch_8.tar
Browse the code in switch_8.tar
............SPECIFICATION
..................... Switch Specification
..................... Packet Format
..................... Configuration
..................... Interface Specification
..VERIFICATION PLAN
..................... Overview
..................... Feature Extraction
..................... Stimulus Generation Plan
..................... Verification Environment
..PHASE 1 TOP
..................... Interface
..................... Top Module
.PHASE 2 CONFIGURATION
..................... Configuration
..................... Updates To Top Module
Phase 1) We will develop the interfaces, and connect it to DUT in top module.
Phase 3) We will develop the Environment class and Simple testcase and simulate them.
Phase 4) We will develop packet class based on the stimulus plan. We will also write a small
code to test the packet class implementation.
Phase 8) We will develop scoreboard which does the comparison of the expected packet with
the actual packet received from the DUT and connect it to driver and receiver in Environment
class.
Installing Uvm Library
1)Go to http://www.accellera.org/activities/vip/
2)Download the uvm*.tar.gz file.
3)Untar the file.
4)Go to the extracted directory : cd uvm*\uvm\src
5)Set the UVM_HOME path : setenv UVM_HOME `pwd`
(This is required to run the examples which are downloaded from this site)
6)Go to examples : cd ../examples/hello_world/uvm/
7)Compile the example using :
your_tool_compilation_command -f compile_<toolname>.f
(example for questasim use : qverilog -f compile_questa.f)
8)Run the example.
SPECIFICATION
Switch Specification:
This is a simple switch. Switch is a packet based protocol. Switch drives the incoming packet
which comes from the input port to output ports based on the address contained in the packet.
The switch has a one input port from which the packet enters. It has four output ports where the
packet is driven out.
Packet Format:
Packet contains 3 parts. They are Header, data and frame check sequence.
Packet width is 8 bits and the length of the packet can be between 4 bytes to 259 bytes.
Packet header:
Packet header contains three fields DA, SA and length.
DA: Destination address of the packet is of 8 bits. The switch drives the packet to respective
ports based on this destination address of the packets. Each output port has 8-bit unique port
address. If the destination address of the packet matches the port address, then switch drives the
packet to the output port.
Length: Length of the data is of 8 bits and from 0 to 255. Length is measured in terms of
bytes.
If Length = 0, it means data length is 0 bytes
If Length = 1, it means data length is 1 bytes
If Length = 2, it means data length is 2 bytes
If Length = 255, it means data length is 255 bytes
Configuration:
Switch has four output ports. These output ports address have to be configured to a unique
address. Switch matches the DA field of the packet with this configured port address and sends
the packet on to that port. Switch contains a memory. This memory has 4 locations, each can
store 8 bits. To configure the switch port address, memory write operation has to be done using
memory interface. Memory address (0,1,2,3) contains the address of port(0,1,2,3) respectively.
Interface Specification:
The Switch has one input Interface, from where the packet enters and 4 output interfaces from
where the packet comes out and one memory interface, through the port address can be
configured. Switch also has a clock and asynchronous reset signal.
MEMORY INTERFACE:
Through memory interfaced output port address are configured. It accepts 8 bit data to be written
to memory. It has 8 bit address inputs. Address 0,1,2,3 contains the address of the port 0,1,2,3
respectively.
All the signals are active high and are synchronous to the positive edge of clock signal.
To configure a port address,
1. Assert the mem_en signal.
2. Asser the mem_rd_wr signal.
3. Drive the port number (0 or 1 or 2 or 3) on the mem_add signal
4. Drive the 8 bit port address on to mem_data signal.
INPUT PORT
Packets are sent into the switch using input port.
All the signals are active high and are synchronous to the positive edge of clock signal.
input port has 2 input signals. They are
input [7:0] data;
input data_status;
To send the packet in to switch,
1. Assert the data_status signal.
2. Send the packet on the data signal byte by byte.
3. After sending all the data bytes, deassert the data_status signal.
4. There should be at least 3 clock cycles difference between packets.
OUTPUT PORT
Switch sends the packets out using the output ports. There are 4 ports, each having data, ready
and read signals. All the signals are active high and are synchronous to the positive edge of
clock signal.
Signal list is
output [7:0] port0;
output [7:0] port1;
output [7:0] port2;
output [7:0] port3;
output ready_0;
output ready_1;
output ready_2;
output ready_3;
input read_0;
input read_1;
input read_2;
input read_3;
When the data is ready to be sent out from the port, switch asserts ready_* signal high indicating
that data is ready to be sent.
If the read_* signal is asserted, when ready_* is high, then the data comes out of the port_*
signal after one clock cycle.
RTL code:
RTL code is attached with the tar files. From the Phase 1, you can download the tar files.
VERIFICATION PLAN
Overview
This Document describes the Verification Plan for Switch. The Verification Plan is based on
System Verilog Hardware Verification Language. The methodology used for Verification is
Constraint random coverage driven verification.
Feature Extraction
This section contains list of all the features to be verified.
1)ID: Configuration
Description: Configure all the 4 port address with unique values.
2)ID: Packet DA
Description: DA field of packet should be any of the port address. All the 4 port address should
be used.
3) ID : Packet payload
Description: Length can be from 1 to 255. Send packets with all the lengths.
4) ID: Length
Description:
Length field contains length of the payload.
5) ID: FCS
Description:
Good FCS: Send packet with good FCS.
Bad FCS: Send packet with corrupted FCS.
Stimulus Generation Plan
1) Packet DA: Generate packet DA with the configured address.
2) Payload length: generate payload length ranging from 2 to 255.
3) Generate good and bad FCS.
Verification Environment
PHASE 1 TOP
In phase 1,
1) We will write SystemVerilog Interfaces for input port, output port and memory port.
2) We will write Top module where testcase and DUT instances are done.
3) DUT and interfaces are connected in top module.
4) We will implement Clock generator in top module.
Interface
In the interface.sv file, declare the 3 interfaces in the following way.
All the interfaces has clock as input.
All the signals in interface are wire type.
All the signals are synchronized to clock except reset in clocking block.
This approach will avoid race conditions between the design and the verification environment.
Define the set-up and hold time using parameters.
Signal directional w.r.t TestBench is specified with modport.
Interface Source Code
`ifndef GUARD_INTERFACE
`define GUARD_INTERFACE
//////////////////////////////////////////
// Interface declaration for the memory///
//////////////////////////////////////////
interface mem_interface(input bit clock);
parameter setup_time = 5ns;
parameter hold_time = 3ns;
endinterface :mem_interface
////////////////////////////////////////////
// Interface for the input side of switch.//
// Reset signal is also passed hear. //
////////////////////////////////////////////
interface input_interface(input bit clock);
wire data_status;
wire [7:0] data_in;
reg reset;
endinterface:input_interface
/////////////////////////////////////////////////
// Interface for the output side of the switch.//
// output_interface is for only one output port//
/////////////////////////////////////////////////
endinterface:output_interface
//////////////////////////////////////////////////
`endif
Top Module
The modules that are included in the source text but are not instantiated are called top modules.
This module is the highest scope of modules. Generally this module is named as "top" and
referenced as "top module". Module name can be anything.
This top-level module will contain the design portion of the simulation.
Do the following in the top module:
`include "uvm.svh"
import uvm_pkg::*;
bit Clock;
initial
begin
#20;
forever #10 Clock = ~Clock;
end
mem_interface mem_intf(Clock);
output_interface output_intf[4](Clock);
5) Connect all the interfaces and DUT. The design which we have taken is in verilog. So
Verilog DUT instance is connected signal by signal.
`ifndef GUARD_TOP
`define GUARD_TOP
/////////////////////////////////////////////////////
// Importing UVM Packages //
/////////////////////////////////////////////////////
`include "uvm.svh"
import uvm_pkg::*;
module top();
/////////////////////////////////////////////////////
// Clock Declaration and Generation //
/////////////////////////////////////////////////////
bit Clock;
initial
begin
#20;
forever #10 Clock = ~Clock;
end
/////////////////////////////////////////////////////
// Memory interface instance //
/////////////////////////////////////////////////////
mem_interface mem_intf(Clock);
/////////////////////////////////////////////////////
// Input interface instance //
/////////////////////////////////////////////////////
input_interface input_intf(Clock);
/////////////////////////////////////////////////////
// output interface instance //
/////////////////////////////////////////////////////
output_interface output_intf[4](Clock);
/////////////////////////////////////////////////////
// DUT instance and signal connection //
/////////////////////////////////////////////////////
endmodule : top
`endif
Download the files:
uvm_switch_1.tar
Browse the code in uvm_switch_1.tar
Command to compile
PHASE 2 CONFIGURATION
In this phase we will implement the configuration class. All the requirements of the testbench
configurations will be declared inside this class. Virtual interfaces required by verification
components driver and receiver for connecting to DUT are declared in this class. We will also
declare 4 variables which will hold the port address of the DUT.
uvm_object does not have the simulation phases and can be used in get_config_object and
set_config_object method. So we will implement the configuration class by extending
uvm_object.
Configuration
endclass : Configuration
`endif
2) Declare All the interfaces which are required in this verification environment.
virtual input_interface.IP input_intf;
virtual mem_interface.MEM mem_intf;
virtual output_interface.OP output_intf[4];
3) Declare 4 variables which holds the device port address.
bit [7:0] device_add[4] ;
t.device_add = this.device_add;
t.input_intf = this.input_intf;
t.mem_intf = this.mem_intf;
t.output_intf = this.output_intf;
return t;
endfunction : create
endclass : Configuration
`endif
Updates To Top Module
In top module we will create an object of the above defined configuration class and update the
interfaces so that all the verification components can access to physical interfaces in top module
using configuration class object.
1) Declare a Configuration class object
Configuration cfg;
2) Construct the configuration object and update the interfaces.
initial begin
cfg = new();
cfg.input_intf = input_intf;
cfg.mem_intf = mem_intf;
cfg.output_intf = output_intf;
3) In top module , we have to call the run_test() method.
run_test();
Top module updates
typedef class Configuration;
module top();
/////////////////////////////////////////////////////
// Clock Declaration and Generation //
/////////////////////////////////////////////////////
bit Clock;
initial
begin
#20;
forever #10 Clock = ~Clock;
end
/////////////////////////////////////////////////////
// Memory interface instance //
/////////////////////////////////////////////////////
mem_interface mem_intf(Clock);
/////////////////////////////////////////////////////
// Input interface instance //
/////////////////////////////////////////////////////
input_interface input_intf(Clock);
/////////////////////////////////////////////////////
// output interface instance //
/////////////////////////////////////////////////////
output_interface output_intf[4](Clock);
/////////////////////////////////////////////////////
// Creat Configuration and Strart the run_test//
/////////////////////////////////////////////////////
Configuration cfg;
initial begin
cfg = new();
cfg.input_intf = input_intf;
cfg.mem_intf = mem_intf;
cfg.output_intf = output_intf;
run_test();
end
/////////////////////////////////////////////////////
// DUT instance and signal connection //
/////////////////////////////////////////////////////
switch DUT (.clk(Clock),
.reset(input_intf.reset),
.data_status(input_intf.data_status),
.data(input_intf.data_in),
.port0(output_intf[0].data_out),
.port1(output_intf[1].data_out),
.port2(output_intf[2].data_out),
.port3(output_intf[3].data_out),
.ready_0(output_intf[0].ready),
.ready_1(output_intf[1].ready),
.ready_2(output_intf[2].ready),
.ready_3(output_intf[3].ready),
.read_0(output_intf[0].read),
.read_1(output_intf[1].read),
.read_2(output_intf[2].read),
.read_3(output_intf[3].read),
.mem_en(mem_intf.mem_en),
.mem_rd_wr(mem_intf.mem_rd_wr),
.mem_add(mem_intf.mem_add),
.mem_data(mem_intf.mem_data));
endmodule : top
`endif
Download the source code
uvm_switch_2.tar
Browse the code in uvm_switch_2.tar
Command to compile
`uvm_component_utils(Environment)
function new(string name , uvm_component parent = null);
super.new(name, parent);
endfunction: new
virtual function void build();
super.build();
uvm_report_info(get_full_name(),"START of build ",UVM_LOW);
uvm_report_info(get_full_name(),"END of build ",UVM_LOW);
endfunction
virtual function void connect();
super.connect();
uvm_report_info(get_full_name(),"START of connect ",UVM_LOW);
endclass : Environment
`endif
Testcase
Now we will implement testcase. In UVM, testcases are implemented by extending uvm_test.
Using uvm_test , provides the ability to select which test to execute using the
UVM_TESTNAME command line option or argument to the uvm_root::run_test task. We will
use UVM_TESTNAME command line argument.
1) Define a testcase by extending uvm_test class.
As we dont have anything now to write in this testcase, just call the global_stop_request() after
some delay.
`uvm_component_utils(test1)
Environment t_env ;
endclass : test1
Download the Source Code
uvm_switch_3.tar
Browse the code in uvm_switch_3.tar
PHASE 4 PACKET
In this Phase, we will develop Transaction as per the verification plan. We will define required
methods and constraints. We will also develop a small logic to test our implementation of this
class.
Packet
We will write the packet class in Packet.sv file. Packet class variables and constraints have been
derived from stimulus generation plan.
da = packer.unpack_field_int($bits(da));
sa = packer.unpack_field_int($bits(sa));
length = packer.unpack_field_int($bits(length));
data.delete();
data = new[length];
foreach(data[i])
data[i] = packer.unpack_field_int(8);
fcs = packer.unpack_field_int($bits(fcs));
endfunction : do_unpack
Packet class source code
`ifndef GUARD_PACKET
`define GUARD_PACKET
`include "uvm.svh"
import uvm_pkg::*;
`uvm_object_utils_begin(Packet)
`uvm_field_int(da, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(sa, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(length, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_array_int(data, UVM_ALL_ON|UVM_NOPACK)
`uvm_field_int(fcs, UVM_ALL_ON|UVM_NOPACK)
`uvm_object_utils_end
da = packer.unpack_field_int($bits(da));
sa = packer.unpack_field_int($bits(sa));
length = packer.unpack_field_int($bits(length));
data.delete();
data = new[length];
foreach(data[i])
data[i] = packer.unpack_field_int(8);
fcs = packer.unpack_field_int($bits(fcs));
endfunction : do_unpack
endclass : Packet
Test The Transaction Implementation
Now we will write a small logic to test our packet implantation. This module is not used in
normal verification.
Define a module and take the instance of packet class. Randomize the packet and call the print
method to analyze the generation. Then pack the packet in to bytes and then unpack bytes and
then call compare method to check all the method implementation.
1) Declare Packet objects and dynamic arrays.
2) In a initial block, randomize the packet, pack the packet in to pkdbytes and then unpack it and
compare the packets.
if(pkt1.randomize)
begin
$display(" Randomization Sucessesfull.");
pkt1.print();
uvm_default_packer.use_metadata = 1;
void'(pkt1.pack_bytes(pkdbytes));
$display("Size of pkd bits %d",pkdbytes.size());
pkt2.unpack_bytes(pkdbytes);
pkt2.print();
if(pkt2.compare(pkt1))
$display(" Packing,Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare *** \n \n");
initial
repeat(10)
if(pkt1.randomize)
begin
$display(" Randomization Successesfull.");
pkt1.print();
uvm_default_packer.use_metadata = 1;
void'(pkt1.pack_bytes(pkdbytes));
$display("Size of pkd bits %d",pkdbytes.size());
pkt2.unpack_bytes(pkdbytes);
pkt2.print();
if(pkt2.compare(pkt1))
$display(" Packing,Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare *** \n \n");
end
else
$display(" *** Randomization Failed ***");
endmodule
Download the Source Code
uvm_switch_4.tar
Browse the code in uvm_switch_4.tar
Randomization Sucessesfull.
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt1 Packet - pkt1@3
--da integral 8 'ha5
--sa integral 8 'ha1
--length integral 8 'h6
--data da(integral) 6 -
----[0] integral 8 'h58
----[1] integral 8 'h60
----[2] integral 8 'h34
----[3] integral 8 'hdd
----[4] integral 8 'h9
----[5] integral 8 'haf
--fcs integral 8 'h75
----------------------------------------------------------------------
Size of pkd bits 10
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt2 Packet - pkt2@5
--da integral 8 'ha5
--sa integral 8 'ha1
--length integral 8 'h6
--data da(integral) 6 -
----[0] integral 8 'h58
----[1] integral 8 'h60
----[2] integral 8 'h34
----[3] integral 8 'hdd
----[4] integral 8 'h9
----[5] integral 8 'haf
--fcs integral 8 'h75
----------------------------------------------------------------------
Packing,Unpacking and compare worked
....
....
....
....PHASE 5 SEQUENCER N SEQUENCE
In this phase we will develop Sequence and Sequencer.
A sequence is series of transaction and sequencer is used to for controlling the flow of
transaction generation.
A sequence of transaction (which we already developed in previous phase) is defined by
extending uvm_sequence class. uvm_sequencer does the generation of this sequence of
transaction, uvm_driver takes the transaction from Sequencer and processes the packet/ drives to
other component or to DUT.
Sequencer
A Sequencer is defined by extending uvm_sequencer. uvm_sequencer has a port
seq_item_export which is used to connect to uvm_driver for transaction transfer.
1) Define a sequencer by extending uvm_sequence.
`ifndef GUARD_SEQUENCER
`define GUARD_SEQUENCER
endclass : Sequencer
`endif
2) We need Device port address, which are in configuration class. So declare a configuration
class object.
Configuration cfg;
`uvm_sequencer_utils(Sequencer)
4) Define the constructor.
4) The algorithm for the transaction should be defined in body() method of the sequence. In this
sequence we will define the algorithm such that alternate transactions for device port 0 and 1 are
generated.
The device addresses are available in configuration object which is in sequencer. Every
sequence has a handle to its sequence through p_sequencer. Using p_sequencer handle, access
the device address.
virtual task body();
forever begin
`uvm_do_with(item, {da == p_sequencer.cfg.device_add[0];} );
`uvm_do_with(item, {da == p_sequencer.cfg.device_add[1];} );
end
endtask : body
Sequence Source Code
class Seq_device0_and_device1 extends uvm_sequence #(Packet);
function new(string name = "Seq_device0_and_device1");
super.new(name);
endfunction : new
Packet item;
`uvm_sequence_utils(Seq_device0_and_device1, Sequencer)
virtual task body();
forever begin
`uvm_do_with(item, {da == p_sequencer.cfg.device_add[0];} );
`uvm_do_with(item, {da == p_sequencer.cfg.device_add[1];} );
end
endtask : body
endclass :Seq_device0_and_device1
One more Sequence
class Seq_constant_length extends uvm_sequence #(Packet);
function new(string name = "Seq_constant_length");
super.new(name);
endfunction : new
Packet item;
`uvm_sequence_utils(Seq_constant_length, Sequencer)
virtual task body();
forever begin
`uvm_do_with(item, {length == 10;da == p_sequencer.cfg.device_add[0];} );
end
endtask : body
endclass : Seq_constant_length
Download the Source Code
uvm_switch_5.tar
Browse the code in uvm_switch_5.tar
PHASE 6 DRIVER
Driver
In this phase we will develop the driver. Driver is defined by extending uvm_driver. Driver takes
the transaction from the sequencer using seq_item_port. This transaction will be driven to DUT
as per the interface specification. After driving the transaction to DUT, it sends the transaction to
scoreboard using uvm_analysis_port.
In driver class, we will also define task for resetting DUT and configuring the DUT. After
completing the driver class implementation, we will instantiate it in environment class and
connect the sequencer to it. We will also update the test case and run the simulation to check the
implementation which we did till now.
`ifndef GUARD_DRIVER
`define GUARD_DRIVER
class Driver extends uvm_driver #(Packet);
endclass : Driver
2) Create a handle to configuration object. Using this object we can get DUT interfaces and DUT
port addresses.
Configuration cfg;
3) Declare input and memory interfaces
virtual input_interface.IP input_intf;
virtual mem_interface.MEM mem_intf;
4) Declare uvm_analysis_port which is used to send packets to scoreboard.
uvm_analysis_port #(Packet) Drvr2Sb_port;
5) Declare component utilities macro.
`uvm_component_utils(Driver)
6) Define the constructor method. Pass the parent object to super class.
function new( string name = "" , uvm_component parent = null) ;
super.new( name , parent );
endfunction : new
7) In the build method and construct Drvr2Sb_port object.
virtual function void build();
super.build();
Drvr2Sb_port = new("Drvr2Sb_port", this);
endfunction : build
8) In the end_of_elaboration() method, get the configuration object using get_config_object and
update the virtual interfaces.
9) Define the reset_dut() method which will be used for resetting the DUT.
10) Define the cfg_dut() method which does the configuration due port address.
@(mem_intf.cb);
mem_intf.cb.mem_add <= i;
mem_intf.cb.mem_data <= cfg.device_add[i];
uvm_report_info(get_full_name(),$psprintf(" Port %0d Address %h
",i,cfg.device_add[i]),UVM_LOW);
end
@(mem_intf.cb);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
11) Define drive() method which will be used to drive the packet to DUT. In this method pack
the packet fields using the pack_bytes() method of the transaction and drive the packed data to
DUT interface.
virtual task drive(Packet pkt);
byte unsigned bytes[];
int pkt_len;
pkt_len = pkt.pack_bytes(bytes);
uvm_report_info(get_full_name(),"Driving packet ...",UVM_LOW);
foreach(bytes[i])
begin
@(input_intf.cb);
input_intf.data_status <= 1 ;
input_intf.data_in <= bytes[i];
end
@(input_intf.cb);
input_intf.data_status <= 0 ;
input_intf.data_in <= 0;
repeat(2) @(input_intf.cb);
endtask : drive
12) Now we will use the above 3 defined methods and update the run() method of uvm_driver.
First call the reset_dut() method and then cfg_dut(). After completing the configuration, in a
forever loop get the transaction from seq_item_port and send it DUT using drive() method and
also to scoreboard using Drvr2SB_port .
`ifndef GUARD_DRIVER
`define GUARD_DRIVER
Configuration cfg;
`uvm_component_utils(Driver)
input_intf.reset <= 1;
repeat (4) @ input_intf.clock;
input_intf.reset <= 0;
@(mem_intf.cb);
mem_intf.cb.mem_add <= i;
mem_intf.cb.mem_data <= cfg.device_add[i];
uvm_report_info(get_full_name(),$psprintf(" Port %0d Address %h
",i,cfg.device_add[i]),UVM_LOW);
end
@(mem_intf.cb);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
foreach(bytes[i])
begin
@(input_intf.cb);
input_intf.data_status <= 1 ;
input_intf.data_in <= bytes[i];
end
@(input_intf.cb);
input_intf.data_status <= 0 ;
input_intf.data_in <= 0;
repeat(2) @(input_intf.cb);
endtask : drive
endclass : Driver
`endif
Environment Updates
We will take the instance of Sequencer and Driver and connect them in Environment class.
Sequencer Seqncr;
Driver Drvr;
2) In build method, construct Seqncr and Drvr object using create() method.
Drvr = Driver::type_id::create("Drvr",this);
Seqncr = Sequencer::type_id::create("Seqncr",this);
Drvr.seq_item_port.connect(Seqncr.seq_item_export);
`ifndef GUARD_ENV
`define GUARD_ENV
Sequencer Seqncr;
Driver Drvr;
Drvr = Driver::type_id::create("Drvr",this);
Seqncr = Sequencer::type_id::create("Seqncr",this);
Drvr.seq_item_port.connect(Seqncr.seq_item_export);
endclass : Environment
`endif
Testcase Updates
1)In the build() method, update the configuration address in the configuration object which in top
module.
virtual function void build();
super.build();
cfg.device_add[0] = 0;
cfg.device_add[1] = 1;
cfg.device_add[2] = 2;
cfg.device_add[3] = 3;
2) In the build() method itself, using set_config_object , configure the configuration object with
the one which is in top module.
with this, the configuration object in Sequencer and Driver will be pointing to the one which in
top module.
set_config_object("t_env.*","Configuration",cfg);
3) In the build method, using set_config_string, configure the default_sequence of the sequencer
to use the sequence which we defined.
set_config_int("*.Seqncr", "count",2);
t_env.Seqncr.print();
Testcase code
`uvm_component_utils(test1)
Environment t_env ;
cfg.device_add[0] = 0;
cfg.device_add[1] = 1;
cfg.device_add[2] = 2;
cfg.device_add[3] = 3;
set_config_object("t_env.*","Configuration",cfg);
set_config_string("*.Seqncr", "default_sequence", "Seq_device0_and_device1");
set_config_int("*.Seqncr", "count",2);
endfunction
t_env.Seqncr.print();
#3000ns;
global_stop_request();
endtask : run
endclass : test1
uvm_switch_6.tar
Browse the code in uvm_switch_6.tar
Receiver class is defined by extending uvm_component class. It will drive the received
transaction to scoreboard using uvm_analysis_port.
endclass : Receiver
`endif
Configuration cfg;
integer id;
5) Declare analysis port which is used by receiver to send the received transaction to scoreboard.
6) Declare the utility macro. This utility macro provides the implementation of creat() and
get_type_name() methods.
`uvm_component_utils(Receiver)
9) In the end_of_elaboration() method, get the configuration object using get_config_object and
update the virtual interfaces.
10) Define the run() method. This method collects the packets from the DUT output interface
and unpacks it into high level transaction using transactions unpack_bytes() method.
output_intf.cb.read <= 0;
@(posedge output_intf.clock);
uvm_report_info(get_full_name(),"Received packet ...",UVM_LOW);
pkt = new();
void'(pkt.unpack_bytes(bytes));
Rcvr2Sb_port.write(pkt);
end
join
endtask : run
Configuration cfg;
integer id;
`uvm_component_utils(Receiver)
output_intf.cb.read <= 0;
@(posedge output_intf.clock);
uvm_report_info(get_full_name(),"Received packet ...",UVM_LOW);
pkt = new();
void'(pkt.unpack_bytes(bytes));
Rcvr2Sb_port.write(pkt);
end
join
endtask : run
endclass : Receiver
Environment Class Updates
We will update the Environment class and take instance of receiver and run the testcase.
1) Declare 4 receivers.
Receiver Rcvr[4];
2) In the build() method construct the Receivers using create() methods. Also update the id
variable of the receiver object.
foreach(Rcvr[i]) begin
Rcvr[i] = Receiver::type_id::create($psprintf("Rcvr%0d",i),this);
Rcvr[i].id = i;
end
`ifndef GUARD_ENV
`define GUARD_ENV
`uvm_component_utils(Environment)
Sequencer Seqncr;
Driver Drvr;
Receiver Rcvr[4];
function new(string name , uvm_component parent = null);
super.new(name, parent);
endfunction: new
foreach(Rcvr[i]) begin
Rcvr[i] = Receiver::type_id::create($psprintf("Rcvr%0d",i),this);
Rcvr[i].id = i;
end
endclass : Environment
`endif
uvm_switch_7.tar
Browse the code in uvm_switch_7.tar
PHASE 8 SCOREBOARD
In this phase we will see the scoreboard implementation.
Scoreboard
Scoreboard is implemented by extending uvm_scorboard. For our requirement, we can use
uvm_in_order_comparator, but we will see develop our own scoreboard by extending
uvm_scorboard. Scoreboard has 2 analysis imports. One is used to for getting the packets from
the driver and other from the receiver. Then the packets are compared and if they don't match,
then error is asserted. For comparison, compare () method of the Packet class is used.
Implement the scoreboard in file Scoreboard.sv.
`uvm_analysis_imp_decl(_rcvd_pkt)
`uvm_analysis_imp_decl(_sent_pkt)
endclass : Scoreboard
`uvm_component_utils(Scoreboard)
Packet exp_que[$];
6) In the constructor, create objects for the above two declared imports.
`uvm_analysis_imp_decl(_rcvd_pkt)
`uvm_analysis_imp_decl(_sent_pkt)
Packet exp_que[$];
if(exp_que.size())
begin
exp_pkt = exp_que.pop_front();
exp_pkt.print();
if( pkt.compare(exp_pkt))
uvm_report_info(get_type_name(),
$psprintf("Sent packet and received packet matched"), UVM_LOW);
else
uvm_report_error(get_type_name(),
$psprintf("Sent packet and received packet mismatched"), UVM_LOW);
end
else
uvm_report_error(get_type_name(),
$psprintf("No more packets to in the expected queue to compare"), UVM_LOW);
endfunction : write_rcvd_pkt
endclass : Scoreboard
`endif
We will take the instance of scoreboard in the environment and connect its ports to driver and
receiver ports.
Scoreboard Sbd;
Sbd = Scoreboard::type_id::create("Sbd",this);
foreach(Rcvr[i])
Rcvr[i].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
`ifndef GUARD_ENV
`define GUARD_ENV
`uvm_component_utils(Environment)
Sequencer Seqncr;
Driver Drvr;
Receiver Rcvr[4];
Scoreboard Sbd;
Drvr = Driver::type_id::create("Drvr",this);
Seqncr = Sequencer::type_id::create("Seqncr",this);
foreach(Rcvr[i]) begin
Rcvr[i] = Receiver::type_id::create($psprintf("Rcvr%0d",i),this);
Rcvr[i].id = i;
end
Sbd = Scoreboard::type_id::create("Sbd",this);
Drvr.seq_item_port.connect(Seqncr.seq_item_export);
Drvr.Drvr2Sb_port.connect(Sbd.Drvr2Sb_port);
foreach(Rcvr[i])
Rcvr[i].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
endclass : Environment
`endif
uvm_switch_8.tar
Browse the code in uvm_switch_8.tar
............PHASE 2 CONFIGURATION
..................... Configuration
..................... Updates To Top Module
.....PHASE 3 ENVIRONMENT N TESTCASE
..................... Environment
..................... Testcase
.PHASE 4 PACKET
..................... Packet
..................... Test The Transaction Implementation
...........PHASE 7 RECEIVER
..................... Receiver
..................... Environment Class Updates
............PHASE 8 SCOREBOARD
..................... Scoreboard
..................... Environment Class Updates
INTRODUCTION
In this tutorial, we will verify the Switch RTL core using OVM in SystemVerilog. Following are
the steps we follow to verify the Switch RTL core.
1) Understand the specification
2) Developing Verification Plan
3) Building the Verification Environment. We will build the Environment in Multiple phases, so
it will be easy for you to lean step by step.
In this verification environment, I will not use agents and monitors to make this tutorial simple
and easy.
Phase 1) We will develop the interfaces, and connect it to DUT in top module.
Phase 2) We will develop the Configuration class.
Phase 3) We will develop the Environment class and Simple testcase and simulate them.
Phase 4) We will develop packet class based on the stimulus plan. We will also write a small
code to test the packet class implementation.
Phase 5) We will develop sequencer and a sample sequences.
Phase 6) We will develop driver and connect it to the Sequencer in to environment.
Phase 7) We will develop receiver and instantiate in environment.
Phase 8) We will develop scoreboard which does the comparison of the expected packet with
the actual packet received from the DUT and connect it to driver and receiver in Environment
class.
I would like to thank to Vishnu Prashant(Vitesse) for teaching me OVM.
SPECIFICATION
Switch Specification:
This is a simple switch. Switch is a packet based protocol. Switch drives the incoming packet
which comes from the input port to output ports based on the address contained in the packet.
The switch has a one input port from which the packet enters. It has four output ports where the
packet is driven out.
Packet Format:
Packet contains 3 parts. They are Header, data and frame check sequence.
Packet width is 8 bits and the length of the packet can be between 4 bytes to 259 bytes.
Packet header:
Packet header contains three fields DA, SA and length.
DA: Destination address of the packet is of 8 bits. The switch drives the packet to respective
ports based on this destination address of the packets. Each output port has 8-bit unique port
address. If the destination address of the packet matches the port address, then switch drives the
packet to the output port.
Length: Length of the data is of 8 bits and from 0 to 255. Length is measured in terms of
bytes.
If Length = 0, it means data length is 0 bytes
If Length = 1, it means data length is 1 bytes
If Length = 2, it means data length is 2 bytes
If Length = 255, it means data length is 255 bytes
Configuration:
Switch has four output ports. These output ports address have to be configured to a unique
address. Switch matches the DA field of the packet with this configured port address and sends
the packet on to that port. Switch contains a memory. This memory has 4 locations, each can
store 8 bits. To configure the switch port address, memory write operation has to be done using
memory interface. Memory address (0,1,2,3) contains the address of port(0,1,2,3) respectively.
Interface Specification:
The Switch has one input Interface, from where the packet enters and 4 output interfaces from
where the packet comes out and one memory interface, through the port address can be
configured. Switch also has a clock and asynchronous reset signal.
MEMORY INTERFACE:
Through memory interfaced output port address are configured. It accepts 8 bit data to be written
to memory. It has 8 bit address inputs. Address 0,1,2,3 contains the address of the port 0,1,2,3
respectively.
INPUT PORT
Packets are sent into the switch using input port.
All the signals are active high and are synchronous to the positive edge of clock signal.
RTL code:
RTL code is attached with the tar files. From the Phase 1, you can download the tar files.
VERIFICATION PLAN
Overview
This Document describes the Verification Plan for Switch. The Verification Plan is based on
System Verilog Hardware Verification Language. The methodology used for Verification is
Constraint random coverage driven verification.
Feature Extraction
This section contains list of all the features to be verified.
1)
ID: Configuration
Description: Configure all the 4 port address with unique values.
2)
ID: Packet DA
Description: DA field of packet should be any of the port address. All the 4 port address should
be used.
3)
ID : Packet payload
Description: Length can be from 1 to 255. Send packets with all the lengths.
4)
ID: Length
Description:
Length field contains length of the payload.
5)
ID: FCS
Description:
Good FCS: Send packet with good FCS.
Bad FCS: Send packet with corrupted FCS.
PHASE 1 TOP
In phase 1,
1) We will write SystemVerilog Interfaces for input port, output port and memory port.
2) We will write Top module where testcase and DUT instances are done.
3) DUT and interfaces are connected in top module.
4) We will implement Clock generator in top module.
Interface
In the interface.sv file, declare the 3 interfaces in the following way.
All the interfaces has clock as input.
All the signals in interface are wire type.
All the signals are synchronized to clock except reset in clocking block.
This approach will avoid race conditions between the design and the verification environment.
Define the set-up and hold time using parameters.
Signal directional w.r.t TestBench is specified with modport.
Interface Source Code
`ifndef GUARD_INTERFACE
`define GUARD_INTERFACE
//////////////////////////////////////////
// Interface declaration for the memory///
//////////////////////////////////////////
////////////////////////////////////////////
// Interface for the input side of switch.//
// Reset signal is also passed hear. //
////////////////////////////////////////////
interface input_interface(input bit clock);
parameter setup_time = 5ns;
parameter hold_time = 3ns;
wire data_status;
wire [7:0] data_in;
reg reset;
`ifndef GUARD_TOP
`define GUARD_TOP
/////////////////////////////////////////////////////
// Importing OVM Packages //
/////////////////////////////////////////////////////
`include "ovm.svh"
import ovm_pkg::*;
module top();
/////////////////////////////////////////////////////
// Clock Declaration and Generation //
/////////////////////////////////////////////////////
bit Clock;
initial
begin
#20;
forever #10 Clock = ~Clock;
end
/////////////////////////////////////////////////////
// Memory interface instance //
/////////////////////////////////////////////////////
mem_interface mem_intf(Clock);
/////////////////////////////////////////////////////
// Input interface instance //
/////////////////////////////////////////////////////
input_interface input_intf(Clock);
/////////////////////////////////////////////////////
// output interface instance //
/////////////////////////////////////////////////////
output_interface output_intf[4](Clock);
/////////////////////////////////////////////////////
// DUT instance and signal connection //
/////////////////////////////////////////////////////
endmodule : top
`endif
Download the files:
ovm_switch_1.tar
Browse the code in ovm_switch_1.tar
Command to compile
ovm_object does not have the simulation phases and can be used in get_config_object and
set_config_object method. So we will implement the configuration class by extending
ovm_object.
Configuration
1) Define configuration class by extending ovm_object
`ifndef GUARD_CONFIGURATION
`define GUARD_CONFIGURATION
class Configuration extends ovm_object;
endclass : Configuration
`endif
2) Declare All the interfaces which are required in this verification environment.
virtual input_interface.IP input_intf;
virtual mem_interface.MEM mem_intf;
virtual output_interface.OP output_intf[4];
3) Declare 4 variables which holds the device port address.
bit [7:0] device0_add ;
bit [7:0] device1_add ;
bit [7:0] device2_add ;
bit [7:0] device3_add ;
4) ovm_object required to define the ovm_object::creat() method.
ovm_object::create method allocates a new object of the same type as this object and returns it
via a base ovm_object handle.
In create method, we have to construct a new object of configuration class and update all the
important fields and return it.
virtual function ovm_object create(string name="");
Configuration t = new();
t.device0_add = this.device0_add;
t.device1_add = this.device1_add;
t.device2_add = this.device2_add;
t.device3_add = this.device3_add;
t.input_intf = this.input_intf;
t.mem_intf = this.mem_intf;
t.output_intf = this.output_intf;
return t;
endfunction : create
Configuration class source code
`ifndef GUARD_CONFIGURATION
`define GUARD_CONFIGURATION
class Configuration extends ovm_object;
virtual input_interface.IP input_intf;
virtual mem_interface.MEM mem_intf;
virtual output_interface.OP output_intf[4];
bit [7:0] device0_add ;
bit [7:0] device1_add ;
bit [7:0] device2_add ;
bit [7:0] device3_add ;
return t;
endfunction : create
endclass : Configuration
`endif
run_test();
module top();
/////////////////////////////////////////////////////
// Clock Declaration and Generation //
/////////////////////////////////////////////////////
bit Clock;
initial
begin
#20;
forever #10 Clock = ~Clock;
end
/////////////////////////////////////////////////////
// Memory interface instance //
/////////////////////////////////////////////////////
mem_interface mem_intf(Clock);
/////////////////////////////////////////////////////
// Input interface instance //
/////////////////////////////////////////////////////
input_interface input_intf(Clock);
/////////////////////////////////////////////////////
// output interface instance //
/////////////////////////////////////////////////////
output_interface output_intf[4](Clock);
/////////////////////////////////////////////////////
// Creat Configuration and Strart the run_test//
/////////////////////////////////////////////////////
Configuration cfg;
initial begin
cfg = new();
cfg.input_intf = input_intf;
cfg.mem_intf = mem_intf;
cfg.output_intf = output_intf;
run_test();
end
/////////////////////////////////////////////////////
// DUT instance and signal connection //
/////////////////////////////////////////////////////
switch DUT (.clk(Clock),
.reset(input_intf.reset),
.data_status(input_intf.data_status),
.data(input_intf.data_in),
.port0(output_intf[0].data_out),
.port1(output_intf[1].data_out),
.port2(output_intf[2].data_out),
.port3(output_intf[3].data_out),
.ready_0(output_intf[0].ready),
.ready_1(output_intf[1].ready),
.ready_2(output_intf[2].ready),
.ready_3(output_intf[3].ready),
.read_0(output_intf[0].read),
.read_1(output_intf[1].read),
.read_2(output_intf[2].read),
.read_3(output_intf[3].read),
.mem_en(mem_intf.mem_en),
.mem_rd_wr(mem_intf.mem_rd_wr),
.mem_add(mem_intf.mem_add),
.mem_data(mem_intf.mem_data));
endmodule : top
`endif
ovm_switch_2.tar
Browse the code in ovm_switch_2.tar
Command to compile
Testcase contains the instance of the environment class. This testcase Creates a Environment
object and defines the required test specific functionality.
Verification environment contains the declarations of the virtual interfaces. These virtual
interfaces are pointed to the physical interfaces which are declared in the top module. These
virtual interfaces are made to point to physical interface in the testcase.
Environment
`ifndef GUARD_ENV
`define GUARD_ENV
endclass : Environment
`endif
2) Declare the utility macro. This utility macro provides the implementation of create() and
get_type_name() methods.
`ovm_component_utils(Environment)
3) Define the constructor. In the constructor, call the super methods and pass the parent object.
Parent is the object in which environment is instantiated.
4) Define build method. In build method, just print messages and super.build() must be called.
This method is automatically called.
Build is the first phase in simulation. This phase is used to construct the child components of the
current class.
endfunction
5) Define connect method. In connect method, just print messages and super.connect() must be
called.
This method is called automatically after the build() method is called. This method is used for
connecting port and exports.
`ovm_component_utils(Environment)
endfunction
endclass : Environment
`endif
Testcase
Now we will implement testcase. In OVM, testcases are implemented by extending ovm_test.
Using ovm_test , provides the ability to select which test to execute using the
OVM_TESTNAME command line option or argument to the ovm_root::run_test task. We will
use OVM_TESTNAME command line argument.
endclass
2) Declare the utility macro.
`ovm_component_utils(test1)
Environment t_env ;
As we dont have anything now to write in this testcase, just call the global_stop_request() after
some delay.
`ovm_component_utils(test1)
Environment t_env ;
function new (string name="test1", ovm_component parent=null);
super.new (name, parent);
t_env = new("t_env",this);
endfunction : new
endclass : test1
ovm_switch_3.tar
Browse the code in ovm_switch_3.tar
PHASE 4 PACKET
In this Phase, we will develop Transaction as per the verification plan. We will define required
methods and constraints. We will also develop a small logic to test our implementation of this
class.
Packet
We will write the packet class in Packet.sv file. Packet class variables and constraints have been
derived from stimulus generation plan.
`ovm_object_utils_begin(Packet)
`ovm_field_int(da, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(sa, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(length, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_array_int(data, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(fcs, OVM_ALL_ON|OVM_NOPACK)
`ovm_object_utils_end
da = packer.unpack_field_int($bits(da));
sa = packer.unpack_field_int($bits(sa));
length = packer.unpack_field_int($bits(length));
data.delete();
data = new[length];
foreach(data[i])
data[i] = packer.unpack_field_int(8);
fcs = packer.unpack_field_int($bits(fcs));
endfunction : do_unpack
`ifndef GUARD_PACKET
`define GUARD_PACKET
`include "ovm.svh"
import ovm_pkg::*;
`ovm_object_utils_begin(Packet)
`ovm_field_int(da, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(sa, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(length, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_array_int(data, OVM_ALL_ON|OVM_NOPACK)
`ovm_field_int(fcs, OVM_ALL_ON|OVM_NOPACK)
`ovm_object_utils_end
da = packer.unpack_field_int($bits(da));
sa = packer.unpack_field_int($bits(sa));
length = packer.unpack_field_int($bits(length));
data.delete();
data = new[length];
foreach(data[i])
data[i] = packer.unpack_field_int(8);
fcs = packer.unpack_field_int($bits(fcs));
endfunction : do_unpack
endclass : Packet
Define a module and take the instance of packet class. Randomize the packet and call the print
method to analyze the generation. Then pack the packet in to bytes and then unpack bytes and
then call compare method to check all the method implementation.
1) Declare Packet objects and dynamic arrays.
2) In a initial block, randomize the packet, pack the packet in to pkdbytes and then unpack it and
compare the packets.
if(pkt1.randomize)
begin
$display(" Randomization Sucessesfull.");
pkt1.print();
ovm_default_packer.use_metadata = 1;
void'(pkt1.pack_bytes(pkdbytes));
$display("Size of pkd bits %d",pkdbytes.size());
pkt2.unpack_bytes(pkdbytes);
pkt2.print();
if(pkt2.compare(pkt1))
$display(" Packing,Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare *** \n \n");
initial
repeat(10)
if(pkt1.randomize)
begin
$display(" Randomization Successesfull.");
pkt1.print();
ovm_default_packer.use_metadata = 1;
void'(pkt1.pack_bytes(pkdbytes));
$display("Size of pkd bits %d",pkdbytes.size());
pkt2.unpack_bytes(pkdbytes);
pkt2.print();
if(pkt2.compare(pkt1))
$display(" Packing,Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare *** \n \n");
end
else
$display(" *** Randomization Failed ***");
endmodule
Download the Source Code
ovm_switch_4.tar
Browse the code in ovm_switch_4.tar
Command to run the simulation
Randomization Sucessesfull.
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt1 Packet - pkt1@3
--da integral 8 'ha5
--sa integral 8 'ha1
--length integral 8 'h6
--data da(integral) 6 -
----[0] integral 8 'h58
----[1] integral 8 'h60
----[2] integral 8 'h34
----[3] integral 8 'hdd
----[4] integral 8 'h9
----[5] integral 8 'haf
--fcs integral 8 'h75
----------------------------------------------------------------------
Size of pkd bits 10
----------------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------------
pkt2 Packet - pkt2@5
--da integral 8 'ha5
--sa integral 8 'ha1
--length integral 8 'h6
--data da(integral) 6 -
----[0] integral 8 'h58
----[1] integral 8 'h60
----[2] integral 8 'h34
----[3] integral 8 'hdd
----[4] integral 8 'h9
----[5] integral 8 'haf
--fcs integral 8 'h75
----------------------------------------------------------------------
Packing,Unpacking and compare worked
Sequencer
`ifndef GUARD_SEQUENCER
`define GUARD_SEQUENCER
`endif
2) We need Device port address, which are in configuration class. So declare a configuration
class object.
Configuration cfg;
`ovm_sequencer_utils(Sequencer)
`ifndef GUARD_SEQUENCER
`define GUARD_SEQUENCER
Configuration cfg;
`ovm_sequencer_utils(Sequencer)
endclass : Sequencer
`endif
Sequence
You can define as many sequences as you want. We will define 2 sequences.
1) Define sequence by extending
endclass: Seq_device0_and_device1
3) Declare utilities macro. With this macro, this sequence is tied to Sequencer.
`ovm_sequence_utils(Seq_device0_and_device1, Sequencer)
4) The algorithm for the transaction should be defined in body() method of the sequence. In this
sequence we will define the algorithm such that alternate transactions for device port 0 and 1 are
generated.
The device addresses are available in configuration object which is in sequencer. Every
sequence has a handle to its sequence through p_sequencer. Using p_sequencer handle, access
the device address.
9) Define the reset_dut() method which will be used for resetting the DUT.
virtual task reset_dut();
ovm_report_info(get_full_name(),"Start of reset_dut() method ",OVM_LOW);
mem_intf.mem_data <= 0;
mem_intf.mem_add <= 0;
mem_intf.mem_en <= 0;
mem_intf.mem_rd_wr <= 0;
input_intf.data_in <= 0;
input_intf.data_status <= 0;
input_intf.reset <= 1;
repeat (4) @ input_intf.clock;
input_intf.reset <= 0;
ovm_report_info(get_full_name(),"End of reset_dut() method ",OVM_LOW);
endtask : reset_dut
10) Define the cfg_dut() method which does the configuration due port address.
virtual task cfg_dut();
ovm_report_info(get_full_name(),"Start of cfg_dut() method ",OVM_LOW);
mem_intf.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.mem_rd_wr <= 1;
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h0;
mem_intf.mem_data <= cfg.device0_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 0 Address %h ",cfg.device0_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h1;
mem_intf.mem_data <= cfg.device1_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 1 Address %h ",cfg.device1_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h2;
mem_intf.mem_data <= cfg.device2_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 2 Address %h ",cfg.device2_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h3;
mem_intf.mem_data <= cfg.device3_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 3 Address %h ",cfg.device3_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_en <=0;
mem_intf.mem_rd_wr <= 0;
mem_intf.mem_add <= 0;
mem_intf.mem_data <= 0;
ovm_report_info(get_full_name(),"End of cfg_dut() method ",OVM_LOW);
endtask : cfg_dut
11) Define drive() method which will be used to drive the packet to DUT. In this method pack
the packet fields using the pack_bytes() method of the transaction and drive the packed data to
DUT interface.
foreach(bytes[i])
begin
@(posedge input_intf.clock);
input_intf.data_status <= 1 ;
input_intf.data_in <= bytes[i];
end
@(posedge input_intf.clock);
input_intf.data_status <= 0 ;
input_intf.data_in <= 0;
repeat(2) @(posedge input_intf.clock);
endtask : drive
12) Now we will use the above 3 defined methods and update the run() method of ovm_driver.
First call the reset_dut() method and then cfg_dut(). After completing the configuration, in a
forever loop get the transaction from seq_item_port and send it DUT using drive() method and
also to scoreboard using Drvr2SB_port .
task run();
Packet pkt;
@(posedge input_intf.clock);
reset_dut();
cfg_dut();
forever begin
seq_item_port.get_next_item(pkt);
Drvr2Sb_port.write(pkt);
@(posedge input_intf.clock);
drive(pkt);
@(posedge input_intf.clock);
seq_item_port.item_done();
end
endtask : run
Driver class source code
`ifndef GUARD_DRIVER
`define GUARD_DRIVER
class Driver extends ovm_driver #(Packet);
Configuration cfg;
virtual input_interface.IP input_intf;
virtual mem_interface.MEM mem_intf;
`ovm_component_utils(Driver)
task run();
Packet pkt;
@(posedge input_intf.clock);
reset_dut();
cfg_dut();
forever begin
seq_item_port.get_next_item(pkt);
Drvr2Sb_port.write(pkt);
@(posedge input_intf.clock);
drive(pkt);
@(posedge input_intf.clock);
seq_item_port.item_done();
end
endtask : run
input_intf.reset <= 1;
repeat (4) @ input_intf.clock;
input_intf.reset <= 0;
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h0;
mem_intf.mem_data <= cfg.device0_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 0 Address %h ",cfg.device0_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h1;
mem_intf.mem_data <= cfg.device1_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 1 Address %h ",cfg.device1_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h2;
mem_intf.mem_data <= cfg.device2_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 2 Address %h ",cfg.device2_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_add <= 8'h3;
mem_intf.mem_data <= cfg.device3_add;
ovm_report_info(get_full_name(),
$psprintf(" Port 3 Address %h ",cfg.device3_add),OVM_LOW);
@(posedge mem_intf.clock);
mem_intf.mem_en <=0;
mem_intf.mem_rd_wr <= 0;
mem_intf.mem_add <= 0;
mem_intf.mem_data <= 0;
foreach(bytes[i])
begin
@(posedge input_intf.clock);
input_intf.data_status <= 1 ;
input_intf.data_in <= bytes[i];
end
@(posedge input_intf.clock);
input_intf.data_status <= 0 ;
input_intf.data_in <= 0;
repeat(2) @(posedge input_intf.clock);
endtask : drive
endclass : Driver
`endif
Environment Updates
We will take the instance of Sequencer and Driver and connect them in Environment class.
Sequencer Seqncr;
Driver Drvr;
2) In build method, construct Seqncr and Drvr object using create() method.
Drvr = Driver::type_id::create("Drvr",this);
Seqncr = Sequencer::type_id::create("Seqncr",this);
Drvr.seq_item_port.connect(Seqncr.seq_item_export);
Environment class code
`ifndef GUARD_ENV
`define GUARD_ENV
class Environment extends ovm_env;
`ovm_component_utils(Environment)
Sequencer Seqncr;
Driver Drvr;
function new(string name , ovm_component parent = null);
super.new(name, parent);
endfunction: new
Drvr = Driver::type_id::create("Drvr",this);
Seqncr = Sequencer::type_id::create("Seqncr",this);
Drvr.seq_item_port.connect(Seqncr.seq_item_export);
endclass : Environment
`endif
Testcase Updates
We will update the testcase and run the simulation.
1)In the build() method, update the configuration address in the configuration object which in top
module.
function void build();
super.build();
cfg.device0_add = 0;
cfg.device1_add = 1;
cfg.device2_add = 2;
cfg.device3_add = 3;
2) In the build() method itself, using set_config_object , configure the configuration object with
the one which is in top module.
with this, the configuration object in Sequencer and Driver will be pointing to the one which in
top module.
set_config_object("t_env.*","Configuration",cfg);
3) In the build method, using set_config_string, configure the default_sequence of the sequencer
to use the sequence which we defined.
set_config_int("*.Seqncr", "count",2);
t_env.Seqncr.print();
Testcase code
`ovm_component_utils(test1)
Environment t_env ;
cfg.device0_add = 0;
cfg.device1_add = 1;
cfg.device2_add = 2;
cfg.device3_add = 3;
set_config_object("t_env.*","Configuration",cfg);
set_config_string("*.Seqncr", "default_sequence", "Seq_device0_and_device1");
set_config_int("*.Seqncr", "count",2);
endfunction
t_env.Seqncr.print();
#1000;
global_stop_request();
endtask : run
endclass : test1
ovm_switch_6.tar
Browse the code in ovm_switch_6.tar
PHASE 7 RECEIVER
In this phase, we will write a receiver and use the receiver in environment class to collect the
packets coming from the switch output_interface.
Receiver
Receiver collects the data bytes from the interface signal. And then unpacks the bytes in to
packet using unpack_bytes method and pushes it into Rcvr2Sb_port for score boarding.
Receiver class is defined by extending ovm_component class. It will drive the received
transaction to scoreboard using ovm_analysis_port.
1) Define Receiver class by extending ovm_component.
`ifndef GUARD_RECEIVER
`define GUARD_RECEIVER
class Receiver extends ovm_component;
endclass : Receiver
`endif
2) Declare configuration class object.
Configuration cfg;
3) Declare an integer to hold the receiver number.
integer id;
4) Declare a virtual interface of dut out put side.
virtual output_interface.OP output_intf;
5) Declare analysis port which is used by receiver to send the received transaction to scoreboard.
ovm_analysis_port #(Packet) Rcvr2Sb_port;
6) Declare the utility macro. This utility macro provides the implementation of creat() and
get_type_name() methods.
`ovm_component_utils(Receiver)
7) Define the constructor.
function new (string name, ovm_component parent);
super.new(name, parent);
endfunction : new
8) Define the build method and construct the Rcvr2Sb_port.
function void build();
super.build();
Rcvr2Sb_port = new("Rcvr2Sb", this);
endfunction : build
9) In the end_of_elaboration() method, get the configuration object using get_config_object and
update the virtual interfaces.
function void end_of_elaboration();
ovm_object tmp;
super.end_of_elaboration();
assert(get_config_object("Configuration",tmp));
$cast(cfg,tmp);
output_intf = cfg.output_intf[id];
endfunction : end_of_elaboration
10) Define the run() method. This method collects the packets from the DUT output interface
and unpacks it into high level transaction using transactions unpack_bytes() method.
virtual task run();
bit [7:0] bytes[];
Packet pkt;
fork
forever
begin
repeat(2) @(posedge output_intf.clock);
wait(output_intf.ready)
output_intf.read <= 1;
output_intf.read <= 0;
@(posedge output_intf.clock);
ovm_report_info(get_full_name(),"Received packet ...",OVM_LOW);
pkt = new();
pkt.unpack_bytes(bytes);
Rcvr2Sb_port.write(pkt);
bytes.delete();
end
join
endtask : run
Receiver class source code
`ifndef GUARD_RECEIVER
`define GUARD_RECEIVER
class Receiver extends ovm_component;
virtual output_interface.OP output_intf;
Configuration cfg;
integer id;
endclass : Receiver
Environment Class Updates
We will update the Environment class and take instance of receiver and run the testcase.
1) Declare 4 receivers.
Receiver Rcvr[4];
2) In the build() method construct the Receivers using create() methods. Also update the id
variable of the receiver object.
foreach(Rcvr[i]) begin
Rcvr[i] = Receiver::type_id::create($psprintf("Rcvr%0d",i),this);
Rcvr[i].id = i;
end
Environment class source code
`ifndef GUARD_ENV
`define GUARD_ENV
class Environment extends ovm_env;
`ovm_component_utils(Environment)
Sequencer Seqncr;
Driver Drvr;
Receiver Rcvr[4];
function new(string name , ovm_component parent = null);
super.new(name, parent);
endfunction: new
function void build();
super.build();
ovm_report_info(get_full_name(),"START of build ",OVM_LOW);
Drvr = Driver::type_id::create("Drvr",this);
Seqncr = Sequencer::type_id::create("Seqncr",this);
foreach(Rcvr[i]) begin
Rcvr[i] = Receiver::type_id::create($psprintf("Rcvr%0d",i),this);
Rcvr[i].id = i;
end
endclass : Environment
`endif
ovm_switch_7.tar
Browse the code in ovm_switch_7.tar
endclass : Scoreboard
2) We need 2 import, one for expected packet which is sent by driver and received packet which
is coming from receiver.
Declare 2 imports using `ovm_analysis_imp_decl macros.
`ovm_analysis_imp_decl(_rcvd_pkt)
`ovm_analysis_imp_decl(_sent_pkt)
`ovm_component_utils(Scoreboard)
Packet exp_que[$];
6) In the constructor, create objects for the above two declared imports.
`ovm_analysis_imp_decl(_rcvd_pkt)
`ovm_analysis_imp_decl(_sent_pkt)
Packet exp_que[$];
if(exp_que.size())
begin
exp_pkt = exp_que.pop_front();
exp_pkt.print();
if( pkt.compare(exp_pkt))
ovm_report_info(get_type_name(),
$psprintf("Sent packet and reeived packet mathed"), OVM_LOW);
else
ovm_report_error(get_type_name(),
$psprintf("Sent packet and reeived packet mismatched"), OVM_LOW);
end
else
ovm_report_error(get_type_name(),
$psprintf("No more packets to in the expected queue to compare"), OVM_LOW);
endfunction : write_rcvd_pkt
endclass : Scoreboard
`endif
We will take the instance of scoreboard in the environment and connect its ports to driver and
receiver ports.
Scoreboard Sbd;
Sbd = Scoreboard::type_id::create("Sbd",this);
Rcvr[0].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
Rcvr[1].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
Rcvr[2].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
Rcvr[3].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
`ifndef GUARD_ENV
`define GUARD_ENV
`ovm_component_utils(Environment)
Sequencer Seqncr;
Driver Drvr;
Receiver Rcvr[4];
Scoreboard Sbd;
Drvr = Driver::type_id::create("Drvr",this);
Seqncr = Sequencer::type_id::create("Seqncr",this);
foreach(Rcvr[i]) begin
Rcvr[i] = Receiver::type_id::create($psprintf("Rcvr%0d",i),this);
Rcvr[i].id = i;
end
Sbd = Scoreboard::type_id::create("Sbd",this);
ovm_report_info(get_full_name(),"END of build ",OVM_LOW);
endfunction
Drvr.seq_item_port.connect(Seqncr.seq_item_export);
Drvr.Drvr2Sb_port.connect(Sbd.Drvr2Sb_port);
Rcvr[0].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
Rcvr[1].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
Rcvr[2].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
Rcvr[3].Rcvr2Sb_port.connect(Sbd.Rcvr2Sb_port);
endclass : Environment
`endif
ovm_switch_8.tar
Browse the code in ovm_switch_8.tar
............PHASE 6 DRIVER
..................... Driver Class Source Code
..................... Environment Class Source Code
..PHASE 7 RECEIVER
..................... Receiver Class Source Code
..................... Environment Class Source Code
............PHASE 8 SCOREBOARD
..................... Scoreboard Class Source Code
..................... Source Code Of The Environment Class
............PHASE 9 COVERAGE
..................... Source Code Of Coverage Class
INTRODUCTION
In this tutorial, we will verify the Switch RTL core using VMM in SystemVerilog. Following are
the steps we follow to verify the Switch RTL core.
3) Building the Verification Environment. We will build the Environment in Multiple phases, so
it will be easy for you to lean step by step.
Phase 1) We will develop the testcase and interfaces, and integrate them in these with the
DUT in top module.
Phase 3) We will develop reset and configuration methods in Environment class. Then using
these methods, we will reset the DUT and configure the port address.
Phase 4) We will develop a packet class based on the stimulus plan. We will also write a
small code to test the packet class implementation.
Phase 6) We will develop a driver class. Packets are taken from the generator and sent to DUT
using driver.
Phase 7) We will develop receiver class. Receiver collects the packets coming from the output
port of the DUT.
Phase 8) We will develop scoreboard class which does the comparison of the expected packet
with the actual packet received from the DUT.
Phase 9) We will develop coverage class based on the coverage plan. Coverage is sampled
using the driver callbacks.
SPECIFICATION
Switch Specification:
This is a simple switch. Switch is a packet based protocol. Switch drives the incoming packet
which comes from the input port to output ports based on the address contained in the packet.
The switch has a one input port from which the packet enters. It has four output ports where the
packet is driven out.
Packet Format:
Packet contains 3 parts. They are Header, data and frame check sequence.
Packet width is 8 bits and the length of the packet can be between 4 bytes to 259 bytes.
Packet header:
Packet header contains three fields DA, SA and length.
DA: Destination address of the packet is of 8 bits. The switch drives the packet to respective
ports based on this destination address of the packets. Each output port has 8-bit unique port
address. If the destination address of the packet matches the port address, then switch drives the
packet to the output port.
Configuration:
Switch has four output ports. These output ports address have to be configured to a unique
address. Switch matches the DA field of the packet with this configured port address and sends
the packet on to that port. Switch contains a memory. This memory has 4 locations, each can
store 8 bits. To configure the switch port address, memory write operation has to be done using
memory interface. Memory address (0,1,2,3) contains the address of port(0,1,2,4) respectively.
Interface Specification:
The Switch has one input Interface, from where the packet enters and 4 output interfaces from
where the packet comes out and one memory interface, through the port address can be
configured. Switch also has a clock and asynchronous reset signal.
MEMORY INTERFACE:
Through memory interfaced output port address are configured. It accepts 8 bit data to be written
to memory. It has 8 bit address inputs. Address 0,1,2,3 contains the address of the port 0,1,2,3
respectively.
There are 4 input signals to memory interface. They are
input mem_en;
input mem_rd_wr;
input [1:0] mem_add;
input [7:0] mem_data;
All the signals are active high and are synchronous to the positive edge of clock signal.
To configure a port address,
1. Assert the mem_en signal.
2. Asser the mem_rd_wr signal.
3. Drive the port number (0 or 1 or 2 or 3) on the mem_add signal
4. Drive the 8 bit port address on to mem_data signal.
INPUT PORT
Packets are sent into the switch using input port.
All the signals are active high and are synchronous to the positive edge of clock signal.
input port has 2 input signals. They are
input [7:0] data;
input data_status;
To send the packet in to switch,
1. Assert the data_status signal.
2. Send the packet on the data signal byte by byte.
3. After sending all the data bytes, deassert the data_status signal.
4. There should be at least 3 clock cycles difference between packets.
OUTPUT PORT
Switch sends the packets out using the output ports. There are 4 ports, each having data, ready
and read signals. All the signals are active high and are synchronous to the positive edge of
clock signal.
Signal list is
output [7:0] port0;
output [7:0] port1;
output [7:0] port2;
output [7:0] port3;
output ready_0;
output ready_1;
output ready_2;
output ready_3;
input read_0;
input read_1;
input read_2;
input read_3;
When the data is ready to be sent out from the port, switch asserts ready_* signal high indicating
that data is ready to be sent.
If the read_* signal is asserted, when ready_* is high, then the data comes out of the port_*
signal after one clock cycle.
RTL code:
RTL code is attached with the tar files. From the Phase 1, you can download the tar files.
VERIFICATION PLAN
Overview
This Document describes the Verification Plan for Switch. The Verification Plan is based on
System Verilog Hardware Verification Language. The methodology used for Verification is
Constraint random coverage driven verification.
Feature Extraction
This section contains list of all the features to be verified.
1)ID: Configuration
Description: Configure all the 4 port address with unique values.
2)ID: Packet DA
Description: DA field of packet should be any of the port address. All the 4 port address should
be used.
3) ID : Packet payload
Description: Length can be from 0 to 255. Send packets with all the lengths.
4) ID: Length
Description:
Length field contains length of the payload.
Send Packet with correct length field and incorrect length fields.
5) ID: FCS
Description:
Good FCS: Send packet with good FCS.
Bad FCS: Send packet with corrupted FCS.
Stimulus Generation Plan
1) Packet DA: Generate packet DA with the configured address.
2) Payload length: generate payload length ranging from 2 to 255.
3) Correct or Incorrect Length field.
4) Generate good and bad FCS.
Coverage Plan
1) Cover all the port address configurations.
2) Cover all the packet lengths.
3) Cover all correct and incorrect length fields.
4) Cover good and bad FCS.
5) Cover all the above combinations.
Verification Environment
PHASE 1 TOP
In phase 1,
1) We will write SystemVerilog Interfaces for input port, output port and memory port.
2) We will write Top module where testcase and DUT instances are done.
3) DUT and TestBench interfaces are connected in top module.
4) Clock is generator in top module.
This approach will avoid race conditions between the design and the verification environment.
Define the set-up and hold time using parameters.
Signal directional w.r.t TestBench is specified with modport.
`ifndef GUARD_INTERFACE
`define GUARD_INTERFACE
//////////////////////////////////////////
// Interface declaration for the memory///
//////////////////////////////////////////
interface mem_interface(input bit clock);
endinterface :mem_interface
////////////////////////////////////////////
// Interface for the input side of switch.//
// Reset signal is also passed hear. //
////////////////////////////////////////////
interface input_interface(input bit clock);
wire data_status;
wire [7:0] data_in;
wire reset;
endinterface:input_interface
/////////////////////////////////////////////////
// Interface for the output side of the switch.//
// output_interface is for only one output port//
/////////////////////////////////////////////////
#1000;
end
final
$display(" ******************** End of testcase *****************");
endprogram
`endif
Top Module
The modules that are included in the source text but are not instantiated are called top modules.
This module is the highest scope of modules. Generally this module is named as "top" and
referenced as "top module". Module name can be anything.
This top-level module will contain the design portion of the simulation.
Do the following in the top module:
1)Generate the clock signal.
bit Clock;
initial
begin
#20;
forever #10 Clock = ~Clock;
end
2)Do the instances of memory interface.
mem_interface mem_intf(Clock);
3)Do the instances of input interface.
input_interface input_intf(Clock);
4)There are 4 output ports. So do 4 instances of output_interface.
output_interface output_intf[4](Clock);
5)Do the instance of testcase and pass all the above declared interfaces.
testcase TC (mem_intf,input_intf,output_intf);
6)Do the instance of DUT.
switch DUT (.
7)Connect all the interfaces and DUT. The design which we have taken is in verilog. So Verilog
DUT instance is connected signal by signal.
switch DUT (.clk(Clock),
.reset(input_intf.reset),
.data_status(input_intf.data_status),
.data(input_intf.data_in),
.port0(output_intf[0].data_out),
.port1(output_intf[1].data_out),
.port2(output_intf[2].data_out),
.port3(output_intf[3].data_out),
.ready_0(output_intf[0].ready),
.ready_1(output_intf[1].ready),
.ready_2(output_intf[2].ready),
.ready_3(output_intf[3].ready),
.read_0(output_intf[0].read),
.read_1(output_intf[1].read),
.read_2(output_intf[2].read),
.read_3(output_intf[3].read),
.mem_en(mem_intf.mem_en),
.mem_rd_wr(mem_intf.mem_rd_wr),
.mem_add(mem_intf.mem_add),
.mem_data(mem_intf.mem_data));
Top Module Source Code:
`ifndef GUARD_TOP
`define GUARD_TOP
module top();
/////////////////////////////////////////////////////
// Clock Declaration and Generation //
/////////////////////////////////////////////////////
bit Clock;
initial
begin
#20;
forever #10 Clock = ~Clock;
end
/////////////////////////////////////////////////////
// Memory interface instance //
/////////////////////////////////////////////////////
mem_interface mem_intf(Clock);
/////////////////////////////////////////////////////
// Input interface instance //
/////////////////////////////////////////////////////
input_interface input_intf(Clock);
/////////////////////////////////////////////////////
// output interface instance //
/////////////////////////////////////////////////////
output_interface output_intf[4](Clock);
/////////////////////////////////////////////////////
// Program block Testcase instance //
/////////////////////////////////////////////////////
testcase TC (mem_intf,input_intf,output_intf);
/////////////////////////////////////////////////////
// DUT instance and signal connection //
/////////////////////////////////////////////////////
endmodule
`endif
Download the phase 1 files:
vmm_switch_1.tar
Browse the code in vmm_switch_1.tar
Environment class.
Virtual interface declaration.
Defining Environment class constructor.
Defining required methods for execution . Currently these methods will not be implemented
in this phase.
We will not implement all the vmm_env virtual methods in this phase but will we print messages
from these methods to understand the simulation execution.
Testcase contains the instance of the environment class and has access to all the public
declaration of environment class. This testcase Creates a Environment object and calls the run()
method which are defined in the environment class. Run() method runs all the simulation
methods which are defined in environment class.
Verification environment contains the declarations of the virtual interfaces. These virtual
interfaces are pointed to the physical interfaces which are declared in the top module.
Constructor method should be declared with virtual interface as arguments, so that when the
object is created, in the testcase can pass the interfaces in to environment class.
Connecting the virtual interfaces of Environment class to the physical interfaces of top module.
`ifndef GUARD_ENV
`define GUARD_ENV
super.new("Environment ");
In constructor methods, the interfaces which are arguments are connected to the virtual interfaces
of environment class.
this.mem_intf = mem_intf_new ;
this.input_intf = input_intf_new ;
this.output_intf = output_intf_new ;
`ifndef GUARD_ENV
`define GUARD_ENV
class Environment extends vmm_env;
virtual mem_interface.MEM mem_intf ;
virtual input_interface.IP input_intf ;
virtual output_interface.OP output_intf[4] ;
function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );
super.new("Environment ");
this.mem_intf = mem_intf_new ;
this.input_intf = input_intf_new ;
this.output_intf = output_intf_new ;
endclass
`endif
We will create a file Global.sv for global requirement. In this file, define all the port address as
macros in this file.
`ifndef GUARD_GLOBALS
`define GUARD_GLOBALS
`define P0 8'h00
`define P1 8'h11
`define P2 8'h22
`define P3 8'h33
`endif
Now We will update the testcase. Take an instance of the Environment class and call the run
method of the Environment class.
`ifndef GUARD_TESTCASE
`define GUARD_TESTCASE
env = new(mem_intf,input_intf,output_intf);
env.run();
end
final
$display(" ******************** End of testcase *****************");
endprogram
`endif
Download the phase 2 source code:
vmm_switch_2.tar
Browse the code in vmm_switch_2.tar
---------------------------------------------------------------------
Simulation PASSED on /./ (/./) at 0 (0 warnings, 0 demoted errors & 0 demoted
warnings)
---------------------------------------------------------------------
Normal[NOTE] on Environment() at 0:
End of report() method
******************** End of testcase *****************
PHASE 3 RESET
In this phase we will reset and configure the DUT.
The Environment class has reset_dut() method which contains the logic to reset the DUT and
cfg_dut() method which contains the logic to configure the DUT port address.
NOTE: Clocking block signals can be driven only using a non-blocking assignment.
In reset_dut() method.
1) Set all the DUT input signals to a known state. And reset the DUT.
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 0;
mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
`vmm_note(this.log ,$psprintf(" Port 0 Address %h ",`P0));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
`vmm_note(this.log ,$psprintf(" Port 1 Address %h ",`P1));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
`vmm_note(this.log ,$psprintf(" Port 2 Address %h ",`P2));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
`vmm_note(this.log ,$psprintf(" Port 3 Address %h ",`P3));
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
repeat(10000) @(input_intf.clock);
vmm_switch_3.tar
Browse the code in vmm_switch_3.tar
---------------------------------------------------------------------
Simulation PASSED on /./ (/./) at 100170 (0 warnings, 0 demoted errors & 0 demoted
warnings)
---------------------------------------------------------------------
Normal[NOTE] on Environment() at 100170:
End of report() method
******************** End of testcase *****************
PHASE 4 PACKET
In this Phase , We will define a packet and then test it whether it is generating as expected.
Packet is modeled using class. Packet class should be able to generate all possible packet types
randomly. Packet class should also implement allocate(), psdisplay(), copy(), compare(),
byte_pack() and byte_unpack() of vmm_data methods.
Using vmm macros, we will create atomic generator and channel for Packet.
We will write the packet class in Packet.sv file. Packet class variables and constraints have been
derived from stimulus generation plan.
Revisit Stimulus Generation Plan
1) Packet DA: Generate packet DA with the configured address.
2) Payload length: generate payload length ranging from 2 to 255.
3) Correct or Incorrect Length field.
4) Generate good and bad FCS.
1) Declare FCS types as enumerated data types. Name members as GOOD_FCS and BAD_FCS.
typedef enum { GOOD_FCS, BAD_FCS } fcs_kind_t;
2) Declare the length type as enumerated data type. Name members as GOOD_LENGTH and
BAD_LENGTH.
typedef enum { GOOD_LENGTH, BAD_LENGTH } length_kind_t;
3) Extend vmm_data class to define Paclet class.
class Packet extends vmm_data;
4) Declare a vmm_log object and construct it.
static vmm_log log = new("Packet","Class");
5) Declare the length type and fcs type variables as rand.
rand fcs_kind_t fcs_kind;
rand length_kind_t length_kind;
6) Declare the packet field as rand. All fields are bit data types. All fields are 8 bit packet array.
Declare the payload as dynamic array.
rand bit [7:0] length;
rand bit [7:0] da;
rand bit [7:0] sa;
rand bit [7:0] data[];//Payload using Dynamic array,size is generated on the fly
rand byte fcs;
7) Constraint the DA field to be any one of the configured address.
constraint address_c { da inside {`P0,`P1,`P2,`P3} ; }
8) Constrain the payload dynamic array size to between 1 to 255.
constraint payload_size_c { data.size inside { [1 : 255]};}
9) Constrain the payload length to the length field based on the length type.
constraint length_kind_c {
(length_kind == GOOD_LENGTH) -> length == data.size;
(length_kind == BAD_LENGTH) -> length == data.size + 2 ; }
Use solve before to direct the randomization to generate first the payload dynamic array size and
then randomize length field.
constraint solve_size_length { solve data.size before length; }
10) Define a port_randomize method. In this method calculate the fcs based on the fcs_kind.
function void post_randomize();
if(fcs_kind == GOOD_FCS)
fcs = 8'b0;
else
fcs = 8'b1;
fcs = cal_fcs();
endfunction : post_randomize
endfunction
14) Define copy() method. copy() method copies the current values of the object instance.
super.copy_data(cpy);
cpy.da = this.da;
cpy.sa = this.sa;
cpy.length = this.length;
cpy.data = new[this.data.size()];
foreach(data[i])
begin
cpy.data[i] = this.data[i];
end
cpy.fcs = this.fcs;
copy = cpy;
endfunction:copy
15) Define Compare() method. Compares the current value of the object instance with the
specified object instance.
If the value is different, FALSE is returned.
virtual function bit compare(input vmm_data to,output string diff,input int kind= -1);
Packet cmp;
if (!$cast(cmp, to))
begin
`vmm_fatal(this.log, "Attempting to compare to a non packet instance");
compare = 0;
diff = "Cannot compare non packets";
return compare;
end
if (this.sa != cmp.sa)
begin
diff = $psprintf("Different SA values: %b != %b", this.sa, cmp.sa);
compare = 0;
return compare;
end
if (this.length != cmp.length)
begin
diff = $psprintf("Different LEN values: %b != %b", this.length, cmp.length);
compare = 0;
return compare;
end
foreach(data[i])
if (this.data[i] != cmp.data[i])
begin
diff = $psprintf("Different data[%0d] values: 0x%h != 0x%h",i, this.data[i], cmp.data[i]);
compare = 0;
return compare;
end
if (this.fcs != cmp.fcs)
begin
diff = $psprintf("Different FCS values: %b != %b", this.fcs, cmp.fcs);
compare = 0;
return compare;
end
endfunction:compare
foreach(data[i])
bytes[3+i] = data[i];
bytes[this.data.size() + 3 ] = fcs;
byte_pack = this.data.size() + 4;
endfunction:byte_pack
`vmm_channel(Packet)
`ifndef GUARD_PACKET
`define GUARD_PACKET
constraint length_kind_c {
(length_kind == GOOD_LENGTH) -> length == data.size;
(length_kind == BAD_LENGTH) -> length == data.size + 2 ; }
function new();
super.new(this.log);
endfunction:new
endfunction
virtual function bit compare(input vmm_data to,output string diff,input int kind= -1);
Packet cmp;
if (!$cast(cmp, to))
begin
`vmm_fatal(this.log, "Attempting to compare to a non packet instance");
compare = 0;
diff = "Cannot compare non packets";
return compare;
end
if (this.sa != cmp.sa)
begin
diff = $psprintf("Different SA values: %b != %b", this.sa, cmp.sa);
compare = 0;
return compare;
end
if (this.length != cmp.length)
begin
diff = $psprintf("Different LEN values: %b != %b", this.length, cmp.length);
compare = 0;
return compare;
end
foreach(data[i])
if (this.data[i] != cmp.data[i])
begin
diff = $psprintf("Different data[%0d] values: 0x%h != 0x%h",i, this.data[i], cmp.data[i]);
compare = 0;
return compare;
end
if (this.fcs != cmp.fcs)
begin
diff = $psprintf("Different FCS values: %b != %b", this.fcs, cmp.fcs);
compare = 0;
return compare;
end
endfunction:compare
foreach(data[i])
bytes[3+i] = data[i];
bytes[this.data.size() + 3 ] = fcs;
byte_pack = this.data.size() + 4;
endfunction:byte_pack
endclass
/////////////////////////////////////////////////////////
//// Create vmm_channel and vmm_atomic_gen for packet////
/////////////////////////////////////////////////////////
`vmm_channel(Packet)
`vmm_atomic_gen(Packet, "Packet Gen")
Now we will write a small program to test our packet implantation. This program block is not
used to verify the DUT.
Write a simple program block and do the instance of packet class. Randomize the packet and call
the display method to analyze the generation. Then pack the packet in to bytes and then unpack
bytes and then call compare method to check all the methods.
Program Block Source Code
program test;
packet pkt1 = new();
packet pkt2 = new();
logic [7:0] bytes[];
initial
repeat(10)
if(pkt1.randomize)
begin
$display(" Randomization Successes full.");
pkt1.display();
void'(pkt1.byte_pack(bytes));
pkt2 = new();
pkt2.byte_unpack(bytes);
if(pkt2.compare(pkt1))
$display(" Packing, Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare ***");
end
else
$display(" *** Randomization Failed ***");
endprogram
Download the packet class with program block.
vmm_switch_4.tar
Browse the code in vmm_switch_4.tar
Randomization Sucessesfull.
Pkt1 packet #0.0.0
Pkt1 da:0x00
Pkt1 sa:0x40
Pkt1 length:0xbe (data.size=190)
Pkt1 data[0]:0xf7 data[1]:0xa6 .... data[188]:0x49 data[189]:0x79
Pkt1 fcs:0x1a
PHASE 5 GENERATOR
In This phase, we will the usage of vmm atomic generator. In phase 4, using`vmm_atomic_gen
macro we defined Packet_atomic_gen.
Packet_atomic_gen atomic_gen;
Packet_channel gen2drvr_chan;
atomic_gen = new("atomic_gen",0,gen2drvr_chan);
atomic_gen.stop_after_n_insts = 10;
atomic_gen.start_xactor();
atomic_gen.stop_xactor();
`ifndef GUARD_ENV
`define GUARD_ENV
Packet_atomic_gen atomic_gen;
Packet_channel gen2drvr_chan;
gen2drvr_chan = new("gen2drvr","chan");
atomic_gen = new("atomic_gen",0,gen2drvr_chan);
atomic_gen.stop_after_n_insts = 10;
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
`vmm_note(this.log ,$psprintf(" Port 0 Address %h ",`P0));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
`vmm_note(this.log ,$psprintf(" Port 1 Address %h ",`P1));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
`vmm_note(this.log ,$psprintf(" Port 2 Address %h ",`P2));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
`vmm_note(this.log ,$psprintf(" Port 3 Address %h ",`P3));
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
atomic_gen.start_xactor();
atomic_gen.stop_xactor();
endclass
`endif
vmm_switch_5.tar
Browse the code in vmm_switch_5.tar
---------------------------------------------------------------------
Simulation PASSED on /./ (/./) at 10170 (0 warnings, 0 demoted errors & 0 demoted
warnings)
---------------------------------------------------------------------
PHASE 6 DRIVER
In phase 6 we will write a driver and then insatiate the driver in environment and send packet in
to DUT. Driver class is defined in Driver.sv file.
In this Driver class, take the packets from the generator and then drives it to the DUT input
interface and then send the packet to a channel for scoreboard purpose.
endclass:Driver_callbacks
2) Declare a virtual input_interface of the switch. We will connect this to the Physical interface
of the top module same as what we did in environment class.
virtual input_interface.IP input_intf;
Packet_channel gen2drv_chan;
4) Define a channel "drv2sb_chan" which is used to send the packets to the score board.
Packet_channel drv2sb_chan;
4) Define new constructor with arguments, virtual input interface and channels "gen2drv_chan"
and "drv2sb_chan".
In the constructor, call the parent constructor and pass the instance name and stream_id.
Connect the channel and virtual interfaces which are passed as constructor arguments to the class
members.
super.new("driver",inst,stream_id);
this.input_intf = input_intf_new;
if(gen2drv_chan == null)
`vmm_fatal(log,"gen2drv_channel is null");
else
this.gen2drv_chan = gen2drv_chan;
if(drv2sb_chan == null)
`vmm_fatal(log,"drvr2sb_channel is null");
else
this.drv2sb_chan = drv2sb_chan;
pkt_len = pkt.byte_pack(pack,0,0);
@(posedge input_intf.clock);
@(input_intf.clock);
input_intf.cb.data_status <= 0 ;
input_intf.cb.data_in <= pack[pkt_len -1];
@(input_intf.clock);
this.drv2sb_chan.put(pkt);
endtask
4) Define the main() method. First call the super.main() method.
super.main();
`vmm_note(this.log," started main task ");
5) In main() method, start a forever thread, which gets the packets from the 'gen2drv_chan' . The
thread iteration has to be block if the channel isempty or stopped.
forever begin
Packet pkt;
wait_if_stopped_or_empty(this.gen2drv_chan);
this.gen2drv_chan.get(pkt);
6) Call the drive() method, which drives the packet to DUT. Call the pre_tans() callback method
using `vmm_callback macro before calling the drive methodand post_trans() callback method
after driving the packet.
`vmm_callback(Driver_callbacks,pre_trans(pkt));
drive(pkt);
`vmm_callback(Driver_callbacks,post_trans(pkt));
Driver Class Source Code:
`ifndef GUARD_DRIVER
`define GUARD_DRIVER
super.new("driver",inst,stream_id);
this.input_intf = input_intf_new;
if(gen2drv_chan == null)
`vmm_fatal(log,"gen2drv_channel is null");
else
this.gen2drv_chan = gen2drv_chan;
if(drv2sb_chan == null)
`vmm_fatal(log,"drvr2sb_channel is null");
else
this.drv2sb_chan = drv2sb_chan;
pkt_len = pkt.byte_pack(pack,0,0);
@(posedge input_intf.clock);
@(input_intf.clock);
input_intf.cb.data_status <= 0 ;
input_intf.cb.data_in <= pack[pkt_len -1];
@(input_intf.clock);
this.drv2sb_chan.put(pkt);
endtask
task main();
super.main();
`vmm_note(this.log," started main task ");
forever begin
Packet pkt;
wait_if_stopped_or_empty(this.gen2drv_chan);
this.gen2drv_chan.get(pkt);
`vmm_callback(Driver_callbacks,pre_trans(pkt));
drive(pkt);
`vmm_callback(Driver_callbacks,post_trans(pkt));
end
endtask
endclass
`endif
Now we will take the instance of the driver in the environment class.
1) Declare a channel "drvr2sb_chan" which will be used to connect the scoreboard and driver.
Packet_channel drvr2sb_chan;
2) Declare a driver object "drvr".
Driver drvr;
drvr2sb_chan = new("drvr2sb","chan");
4) In build method, construct the driver object. Pass the input_intf and drvr2sb_chan channel and
gen2drvr_chan channel.
drvr = new("Drvr",0,input_intf,gen2drvr_chan,drvr2sb_chan);
5) To start sending the packets to the DUT, call the start method of "drvr" in the start method of
Environment class.
drvr.start();
drvr.stop_xactor();
`ifndef GUARD_ENV
`define GUARD_ENV
Packet_atomic_gen atomic_gen;
Driver drvr;
Packet_channel gen2drvr_chan;
Packet_channel drvr2sb_chan;
drvr2sb_chan = new("drvr2sb","chan");
drvr = new("Drvr",0,input_intf,gen2drvr_chan,drvr2sb_chan);
atomic_gen = new("atomic_gen",0,gen2drvr_chan);
atomic_gen.stop_after_n_insts = 10;
`vmm_note(this.log,"End of build() method ");
endfunction
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
`vmm_note(this.log ,$psprintf(" Port 0 Address %h ",`P0));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
`vmm_note(this.log ,$psprintf(" Port 1 Address %h ",`P1));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
`vmm_note(this.log ,$psprintf(" Port 2 Address %h ",`P2));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
`vmm_note(this.log ,$psprintf(" Port 3 Address %h ",`P3));
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
drvr.start_xactor();
drvr.stop_xactor();
endclass
`endif
vmm_switch_6.tar
Browse the code in vmm_switch_6.tar
---------------------------------------------------------------------
Simulation PASSED on /./ (/./) at 10170 (0 warnings, 0 demoted errors & 0 demoted
warnings)
---------------------------------------------------------------------
In this phase, we will write a receiver and use the receiver in environment class to collect the
packets coming from the switch output_interface.
Receiver collects the data bytes from the interface signal. And then unpacks the bytes in to
packet and pushes it into channel for score boarding.
1) Declare a virtual output_interface. We will connect this to the Physical interface of the top
module, same as what we did in environment class.
virtual output_interface.OP output_intf;
2) Declare a channel "rcvr2sb_chan" which is used to send the packets to the score board
Packet_channel rcvr2sb_chan;
3) Define new constructor with arguments, virtual input interface and a channel which is used to
send packets from the receiver to scoreboard. Implement the restof the logic as it was done in the
driver constructor.
super.new("Receiver",inst,stream_id);
this.output_intf = output_intf_new ;
if(rcvr2sb_chan == null)
`vmm_fatal(log,"rcvr2sb_channel is null");
else
this.rcvr2sb_chan = rcvr2sb_chan;
endfunction : new
forever
begin
repeat(2) @(posedge output_intf.clock);
wait(output_intf.cb.ready)
output_intf.cb.read <= 1;
repeat(2) @(posedge output_intf.clock);
while (output_intf.cb.ready)
begin
bytes = new[bytes.size + 1](bytes);
bytes[bytes.size - 1] = output_intf.cb.data_out;
@(posedge output_intf.clock);
end
bytes[bytes.size - 1] = output_intf.cb.data_out;
output_intf.cb.read <= 0;
@(posedge output_intf.clock);
`vmm_note(this.log,"Received a packet ");
pkt = new();
pkt.byte_unpack(bytes);
pkt.display("rcvr");
rcvr2sb_chan.put(pkt);
bytes.delete();
end
super.new("Receiver",inst,stream_id);
this.output_intf = output_intf_new ;
if(rcvr2sb_chan == null)
`vmm_fatal(log,"rcvr2sb_channel is null");
else
this.rcvr2sb_chan = rcvr2sb_chan;
endfunction : new
task main();
logic [7:0] bytes[];
Packet pkt;
super.main();
`vmm_note(this.log," started main task ");
forever
begin
repeat(2) @(posedge output_intf.clock);
wait(output_intf.cb.ready)
output_intf.cb.read <= 1;
repeat(2) @(posedge output_intf.clock);
while (output_intf.cb.ready)
begin
bytes = new[bytes.size + 1](bytes);
bytes[bytes.size - 1] = output_intf.cb.data_out;
@(posedge output_intf.clock);
end
bytes[bytes.size - 1] = output_intf.cb.data_out;
output_intf.cb.read <= 0;
@(posedge output_intf.clock);
`vmm_note(this.log,"Received a packet ");
pkt = new();
pkt.byte_unpack(bytes);
pkt.display("rcvr");
rcvr2sb_chan.put(pkt);
bytes.delete();
end
endtask : main
endclass
`endif
Now we will take the instance of the receiver in the environment class.
1) Declare a channel "rcvr2sb_chan" which will be used to connect the scoreboard and receiver.
Packet_channel rcvr2sb_chan;
Receiver rcvr[4];
3) In build method, construct the rcvr2sb_chan.
rcvr2sb_chan = new("rcvr2sb","chan");
4) In build method, construct the receiver object. Pass the output_intf and rcvr2sb_chan. There
are 4 output interfaces and receiver objects. We will connect one receiver for one output
interface.
foreach(rcvr[i])
rcvr[i] = new($psprintf("Rcvr-%0d",i),i,output_intf[i],rcvr2sb_chan);
5) To start the receiver activities, call the start_xactor() method of rcvr objects in the start()
method of Environment class.
rcvr[0].start_xactor();
rcvr[1].start_xactor();
rcvr[2].start_xactor();
rcvr[3].start_xactor();
6) Call the stop_xactor() method of the receiver object in the stop() method of the Environment .
rcvr[0].stop_xactor();
rcvr[1].stop_xactor();
rcvr[2].stop_xactor();
rcvr[3].stop_xactor();
`ifndef GUARD_ENV
`define GUARD_ENV
Packet_atomic_gen atomic_gen;
Driver drvr;
Receiver rcvr[4];
Packet_channel gen2drvr_chan;
Packet_channel drvr2sb_chan;
Packet_channel rcvr2sb_chan;
rcvr2sb_chan = new("rcvr2sb","chan");
atomic_gen = new("atomic_gen",0,gen2drvr_chan);
atomic_gen.stop_after_n_insts = 10;
drvr = new("Drvr",0,input_intf,gen2drvr_chan,drvr2sb_chan);
foreach(rcvr[i])
rcvr[i] = new($psprintf("Rcvr-%0d",i),i,output_intf[i],rcvr2sb_chan);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
`vmm_note(this.log ,$psprintf(" Port 0 Address %h ",`P0));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
`vmm_note(this.log ,$psprintf(" Port 1 Address %h ",`P1));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
`vmm_note(this.log ,$psprintf(" Port 2 Address %h ",`P2));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
`vmm_note(this.log ,$psprintf(" Port 3 Address %h ",`P3));
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
rcvr[0].start_xactor();
rcvr[1].start_xactor();
rcvr[2].start_xactor();
rcvr[3].start_xactor();
rcvr[0].stop_xactor();
rcvr[1].stop_xactor();
rcvr[2].stop_xactor();
rcvr[3].stop_xactor();
endclass
`endif
vmm_switch_7.tar
Browse the code in vmm_switch_7.tar
Run the command:
vcs -sverilog -f filelist -R -ntb_opts rvm
---------------------------------------------------------------------
Simulation PASSED on /./ (/./) at 10170 (0 warnings, 0 demoted errors & 0 demoted
warnings)
---------------------------------------------------------------------
PHASE 8 SCOREBOARD
In this phase we will see the scoreboard implementation. Vmm has scoreboard classes with lot of
features. For this example, we will write a simple scoreboard which is implemented using the
vmm_xactor.
Scoreboard has 2 channels. One is used to for getting the packets from the driver and other from
the receiver. Then the packets are compared and if they don't match, then error is asserted. For
comparison, compare () method of the Packet class is used.
Packet_channel drvr2sb_chan;
Packet_channel rcvr2sb_chan;
2) Declare a constructor method with drvr2sb_chan , rcvr2sb_chan , a string for instance name
and stream_id as arguments.
3) Call the super.new() method and Connect the channels of the constructor to the channels of
the scoreboard.
super.new("sb",inst,stream_id);
if(drvr2sb_chan == null)
`vmm_fatal(this.log,"drvr2sb_channel is not constructed");
else
this.drvr2sb_chan = drvr2sb_chan;
if(rcvr2sb_chan == null)
`vmm_fatal(this.log,"rcvr2sb_channel is not constructed");
else
this.rcvr2sb_chan = rcvr2sb_chan;
4) Define vmm_xactor main() method. First call the super.main() method and then do the
following steps forever.
Wait until there is a packet is in "rcvr2sb_chan". Then pop the packet from channel.
rcvr2sb_chan.get(pkt_rcv);
$display(" %0d : Scorebooard : Scoreboard received a packet from receiver ",$time);
drvr2sb_chan.get(pkt_exp);
Compare both packets and assert an error if the comparison fails using `vmm_error.
if(pkt_rcv.compare(pkt_exp,msg))
$display(" %0d : Scoreboard :Packet Matched ",$time);
else
`vmm_error(this.log,$psprintf(" Packet MissMatched \n %s ",msg));
Packet_channel drvr2sb_chan;
Packet_channel rcvr2sb_chan;
function new(string inst = "class",
int unsigned stream_id = -1,
Packet_channel drvr2sb_chan = null,
Packet_channel rcvr2sb_chan = null);
super.new("sb",inst,stream_id);
if(drvr2sb_chan == null)
`vmm_fatal(this.log,"drvr2sb_channel is not constructed");
else
this.drvr2sb_chan = drvr2sb_chan;
if(rcvr2sb_chan == null)
`vmm_fatal(this.log,"rcvr2sb_channel is not constructed");
else
this.rcvr2sb_chan = rcvr2sb_chan;
endfunction:new
task main();
Packet pkt_rcv,pkt_exp;
string msg;
super.main();
forever
begin
rcvr2sb_chan.get(pkt_rcv);
$display(" %0d : Scoreboard : Scoreboard received a packet from receiver ",$time);
drvr2sb_chan.get(pkt_exp);
if(pkt_rcv.compare(pkt_exp,msg))
$display(" %0d : Scoreboard :Packet Matched ",$time);
else
`vmm_error(this.log,$psprintf(" Packet MissMatched \n %s ",msg));
end
endtask : main
endclass
`endif
Now we will see how to connect the scoreboard in the Environment class.
Scoreboard sb;
2) Construct the scoreboard in the build() method. Pass the drvr2sb_chan and rcvr2sb_chan
channels to the score board constructor.
sb = new("Sb",0,drvr2sb_chan,rcvr2sb_chan);
sb.start_xactor();
`ifndef GUARD_ENV
`define GUARD_ENV
Packet_atomic_gen atomic_gen;
Driver drvr;
Receiver rcvr[4];
Scoreboard sb;
Packet_channel gen2drvr_chan;
Packet_channel drvr2sb_chan;
Packet_channel rcvr2sb_chan;
sb = new("Sb",0,drvr2sb_chan,rcvr2sb_chan);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
`vmm_note(this.log ,$psprintf(" Port 0 Address %h ",`P0));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
`vmm_note(this.log ,$psprintf(" Port 1 Address %h ",`P1));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
`vmm_note(this.log ,$psprintf(" Port 2 Address %h ",`P2));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
`vmm_note(this.log ,$psprintf(" Port 3 Address %h ",`P3));
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
sb.start_xactor();
sb.stop_xactor();
endclass
`endif
vmm_switch_8.tar
Browse the code in vmm_switch_8.tar
size 6 ****
Normal[NOTE] on Receiver(Rcvr-3) at 890:
Received a packet
rcvr packet #0.0.0
rcvr da:0x33
rcvr sa:0x6b
rcvr length:0x03 (data.size=2)
rcvr data[0]:0x32 data[1]:0x27
rcvr fcs:0x1c
---------------------------------------------------------------------
Simulation PASSED on /./ (/./) at 10170 (0 warnings, 0 demoted errors & 0 demoted
warnings)
---------------------------------------------------------------------
PHASE 9 COVERAGE
In this phase we will write the functional coverage for switch protocol. Functional coverage is
written in DrvrCovCallback.sv file. After running simulation, you will analyze the coverage
results and find out if some test scenarios have not been exercised and write tests to exercise
them.
We will do the coverage sampling in the driver porst_trans() callback method which we
developed in PHASE_6.
1) Define a cover group with following cover points. Define this cover group in a class which
extends Driver_callbacks.
da : coverpoint pkt.da {
bins p0 = { `P0 };
bins p1 = { `P1 };
bins p2 = { `P2 };
bins p3 = { `P3 }; }
function new();
switch_coverage = new();
endfunction : new
3) Write task which calls the sample method to cover the points.
covergroup switch_coverage;
function new();
switch_coverage = new();
endfunction : new
endclass:DrvrCovCallback
`endif
DrvrCovCallback cov_cb;
cov_cb = new();
3) In the Build method, call the append_callback() of the drvr and pass the cov_cb
object.
drvr.append_callback(cov_cb);
`ifndef GUARD_ENV
`define GUARD_ENV
class Environment extends vmm_env;
Packet_atomic_gen atomic_gen;
Driver drvr;
Receiver rcvr[4];
Scoreboard sb;
DrvrCovCallback cov_cb;
Packet_channel gen2drvr_chan;
Packet_channel drvr2sb_chan;
Packet_channel rcvr2sb_chan;
cov_cb = new();
drvr.append_callback(cov_cb);
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
`vmm_note(this.log ,$psprintf(" Port 0 Address %h ",`P0));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
`vmm_note(this.log ,$psprintf(" Port 1 Address %h ",`P1));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
`vmm_note(this.log ,$psprintf(" Port 2 Address %h ",`P2));
@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
`vmm_note(this.log ,$psprintf(" Port 3 Address %h ",`P3));
@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;
endclass
`endif
AVM INTRODUCTION
AVM provides base classes to improve user productivity. AVM has TLM interfaces to
communicate between testbench components. AVM liberary has the following base classes.
Verification components can be class or modules. Modules are handy for HDL users. One of the
main advantage of using module based avm testbench is it supports system verilog assertions.In
SystemVerilog ,Assertions canot be part of class.
Where as class are more flexible as they are OO. Randomization is easy with class. Dynamic
instancsation and classes can be used as varibles for communication.
Hear Im going to discussing the avm base classes which are used in the example. For more
details refer to avm cookbook.
Tlm:
All the verification components use TLM interfacess whether they are class based or module
based. Main operations on the TLM or put,get and peek.
Initiator puts transaction to a target and can get trasaction from a target.
TLM supports unidirectional and bidirectional data flow. tlm_fifos are bases for TLM. tlm_fifo
has put,peek(get a copy) and get interface for communication tranctions. It also has
put_ap,get_ap(analaysis ports for commucating with score board or covarage model).
EXAMPLE
Declare a tlm_fifo:
tlm_fifo#(packet) gen2drv;
gen.put_port = gen2drv.blocking_put_export;
drvr.get_port = gen2drv.blocking_get_export;
Noe put can be done in gen and get can be done in drvr.
Building Blocks
There are two types of bases class used for building AVM components.
1)avm_named_component
2)avm_verificatino_component.
avm_named_components has information about the hierarchy of the instantiatio and are used for
TLM port and export connections. This also has built in message controlling system.
Avm_transactors:
Avm transaction is base class for creating tranctions. In order to allow the avm and tlm libraries
to work, we need methods to print, compare and clone transactions.
Avm_env:
avm_env class is the top level of testbench. avm_env has do_test() method which starts building
and execution of the testbench components.
Avm_messaging:
avm_messaging supports four types of severity levels. They are
MESSAGE,WARNING,ERROR,FATAL
avm_messaging supports four types of actions. They are DISPLAY,LOG,COUNT,EXIT.
Each of the actions are define by severity and id.
DUT SPECIFICATION
This DUT is a simple switch, which can drive the incoming packet to destination ports based on
the address contained in the packet.
The dut contain one input interface from which the packet enters the dut. It has four output
interfaces where the packet is driven out.
Packet format:
Packet : Header, data and frame check sequence. Packet width is 8 bits and the length of the
packet can be between 4 bytes to 259 bytes.
Packet header:
Packet header contains three fields DA, SA and length.
DA: Destination address of the packet. It is 8 bits. The switch drives the packet to respective
ports based on this destination address of the packets.
SA: Source address of the packet from where it originate.
Length: This is the length of the data. It can be from 0 to 255.
FCS: This field contains the security check of the packet. It is calculated over the header and
data.
Configuration:
Dut has four output ports. These output ports have to be configure to a address. Dut matches the
DA field of the packet with this configured port address and sends the packet on to that port. To
configure the dut, a memory interface is provided. The address of the ports should be unique. It
is 8 bits wide. Memory address (0,1,2,3) contains the address of port(0,1,2,4) respectively.
Interface Specification:
The dut has one input Interface, from where the packet enters the dut and 4 output interfaces
from where the packet comes out and one memory interface, through the port address can be
configured.
Memory Interface:
Through memory interfaced output port address are configured. It accepts 8 bit data to be written
to memory. It has 8 bit address inputs. Address 0,1,2,3 contains the address of the port 0,1,2,3
respectively. If the DA feild in the packet matches with the confugured address of any port ,then
the packet comes out of that port.
Input Interface:
The status signal has to be high when data is when packet is sent on to the dut it has to become
low after sending last byte of the packet. 2 clocks gap should be maintained between packets.
Output Interface:
There are 4 ports, each having data, ready and read signals.
When the data is ready to be sent out from the port, dut makes the ready signal high indicating
that data is ready to be sent.
If the read signal is made high when ready is high, then the data comes out of the data signal.
RTL
CODE:rtl.v
module fifo (clk,
reset,
write_enb,
read,
data_in,
data_out,
empty,
full);
input clk;
input reset;
input write_enb;
input read;
input [7:0] data_in;
output [7:0] data_out;
output empty;
output full;
wire clk;
wire write_enb;
wire read;
wire [7:0] data_in;
reg [7:0] data_out;
wire empty;
wire full;
reg [7:0] ram[0:25];
reg tmp_empty;
reg tmp_full;
integer write_ptr;
integer read_ptr;
always@(negedge reset)
begin
data_out = 8'b0000_0000;
tmp_empty = 1'b1;
tmp_full = 1'b0;
write_ptr = 0;
read_ptr = 0;
end
always@(negedge reset)
begin
error = 1'b0;
data_out = 8'b0000_0000;
addr = 8'b00000000;
write_enb_r = 3'b000;
fsm_write_enb = 1'b0;
state_r = 4'b0000;
state = 4'b0000;
parity = 8'b0000_0000;
parity_delayed = 8'b0000_0000;
sus_data_in = 1'b0;
end
assign busy = sus_data_in;
always @(data_status) begin : addr_mux
if (data_status == 1'b1) begin
case (data_in)
mem0 : begin
write_enb_r[0] = 1'b1;
write_enb_r[1] = 1'b0;
write_enb_r[2] = 1'b0;
write_enb_r[3] = 1'b0;
end
mem1 : begin
write_enb_r[0] = 1'b0;
write_enb_r[1] = 1'b1;
write_enb_r[2] = 1'b0;
write_enb_r[3] = 1'b0;
end
mem2 : begin
write_enb_r[0] = 1'b0;
write_enb_r[1] = 1'b0;
write_enb_r[2] = 1'b1;
write_enb_r[3] = 1'b0;
end
mem3 : begin
write_enb_r[0] = 1'b0;
write_enb_r[1] = 1'b0;
write_enb_r[2] = 1'b0;
write_enb_r[3] = 1'b1;
end
default :write_enb_r = 3'b000;
endcase
// $display(" data_inii %d ,mem0 %d ,mem1 %d ,mem2 %d
mem3",data_in,mem0,mem1,mem2,mem3);
end //if
end //addr_mux;
always @(posedge clk) begin : fsm_state
state_r <= state;
end //fsm_state;
end
else begin
fsm_write_enb = 1'b0;
end //if
end // of case ADDR_WAIT
PARITY_LOAD : begin
state = ADDR_WAIT;
data_out = data_in;
fsm_write_enb = 1'b0;
end // of case PARITY_LOAD
DATA_LOAD : begin
if ((data_status == 1'b1) &&
(hold == 1'b0)) begin
state = DATA_LOAD;
end
else if ((data_status == 1'b0) &&
(hold == 1'b0)) begin
state = PARITY_LOAD;
end
else begin
state = HOLD_STATE;
end //if
sus_data_in = 1'b0;
if ((data_status == 1'b1) &&
(hold == 1'b0)) begin
data_out = data_in;
fsm_write_enb = 1'b1;
end
else if ((data_status == 1'b0) &&
(hold == 1'b0)) begin
data_out = data_in;
fsm_write_enb = 1'b1;
end
else begin
fsm_write_enb = 1'b0;
end //if
end //end of case DATA_LOAD
HOLD_STATE : begin
if (hold == 1'b1) begin
state = HOLD_STATE;
end
else if ((hold == 1'b0) && (data_status == 1'b0)) begin
state = PARITY_LOAD;
end
else begin
state = DATA_LOAD;
end //if
if (hold == 1'b1) begin
sus_data_in = 1'b1;
fsm_write_enb = 1'b0;
end
else begin
fsm_write_enb = 1'b1;
data_out = data_in;
end //if
end //end of case HOLD_STATE
BUSY_STATE : begin
if (ffee == 1'b0) begin
state = BUSY_STATE;
end
else begin
state = DATA_LOAD;
end //if
if (ffee == 1'b0) begin
sus_data_in = 1'b1;
end
else begin
addr = data_in; // hans
data_out = data_in;
fsm_write_enb = 1'b1;
end //if
end //end of case BUSY_STATE
endcase
end //fsm_core
endmodule //port_fsm
module switch (clk,
reset,
data_status,
data,
port0,
port1,
port2,
port3,
ready_0,
ready_1,
ready_2,
ready_3,
read_0,
read_1,
read_2,
read_3,
mem_en,
mem_rd_wr,
mem_add,
mem_data);
input clk;
input reset;
input data_status;
input [7:0] data;
input mem_en;
input mem_rd_wr;
input [1:0] mem_add;
input [7:0] mem_data;
output [7:0] port0;
output [7:0] port1;
output [7:0] port2;
output [7:0] port3;
output ready_0;
output ready_1;
output ready_2;
output ready_3;
input read_0;
input read_1;
input read_2;
input read_3;
wire [7:0] data_out_0;
wire [7:0] data_out_1;
wire [7:0] data_out_2;
wire [7:0] data_out_3;
wire ll0;
wire ll1;
wire ll2;
wire ll3;
wire empty_0;
wire empty_1;
wire empty_2;
wire empty_3;
wire ffee;
wire ffee0;
wire ffee1;
wire ffee2;
wire ffee3;
wire ld0;
wire ld1;
wire ld2;
wire ld3;
wire hold;
wire [3:0] write_enb;
wire [7:0] data_out_fsm;
wire [7:0] addr;
reg [7:0]mem[3:0];
wire reset;
fifo queue_0 (.clk (clk),
.reset (reset),
.write_enb (write_enb[0]),
.read (read_0),
.data_in (data_out_fsm),
.data_out (data_out_0),
.empty (empty_0),
.full (ll0));
always@(posedge clk)
begin
if(mem_en)
if(mem_rd_wr)
begin
mem[mem_add]=mem_data;
///$display("%d %d %d %d %d",mem_add,mem[0],mem[1],mem[2],mem[3]);
end
end
endmodule //router
TOP
Verilog Top
Top level module containts the design and testbench instance. Top module also contains clock
generator. There is no need to instantiate the top module. Testbench and dut instances are
connected using interface instance. Make an instance of env class and creat it. Call the do_test()
method which starts the testbench components.
CODE: top.v
`include "mem_env.sv"
module top();
//As the RTL is in verilog and the SV Interface ports are not used,Connect signals by signal
names
switch switch1 (.clk (intf.clock),
.reset (intf.reset),
.data_status (intf.data_status),
.data (intf.data_in),
.port0 (intf.data_out[0]),
.port1 (intf.data_out[1]),
.port2 (intf.data_out[2]),
.port3 (intf.data_out[3]),
.ready_0 (intf.ready[0]),
.ready_1 (intf.ready[1]),
.ready_2 (intf.ready[2]),
.ready_3 (intf.ready[3]),
.read_0 (intf.read[0]),
.read_1 (intf.read[1]),
.read_2 (intf.read[2]),
.read_3 (intf.read[3]),
.mem_en (intf.mem_en),
.mem_rd_wr (intf.mem_rd_wr),
.mem_add (intf.mem_add),
.mem_data (intf.mem_data));
initial
begin
@(posedge intf.clock);
// Pass the interface to testbench environment.
env = new(intf);
//Call do_test task. extecution of the testbench starts.
env.do_test;
$finish;
end
endmodule //top
INTERFACE
Creat an interface .Define modport to specifies direction. These are used to connect testbench
environment and dut in top module.
CODE:interface
`ifndef INTF
`define INTF
interface switch_if();
bit data_status;
bit [7:0] data_in;
wire [3:0][7:0] data_out;
wire [3:0] ready;
bit [3:0] read;
bit [7:0] mem_data;
bit [1:0] mem_add;
bit reset;
bit mem_en;
bit mem_rd_wr;
bit clock;
modport TB(
output data_status,
output data_in,
input data_out,
input ready,
output read,
output mem_data,
output mem_add,
output reset,
output mem_en,
output mem_rd_wr
);
endinterface:switch_if
`endif
ENVIRONMENT
`include "Configuration.sv"
`include "packet.sv"
`include "mem_driver.sv"
`include "reciever.sv"
`include "score_board.sv"
// specific components
local Configuration cfg;
local driver drvr;
local reciever rcvr_0;
local reciever rcvr_1;
local reciever rcvr_2;
local reciever rcvr_3;
local scoreboard sb;
local generator gen;
local packet pkt;
string msg;
this.intf = intf;
cfg = new("cfg",intf);
pkt = new();
drvr = new("driver",cfg);
rcvr_0 = new("reciever_0",0);
rcvr_1 = new("reciever_1",1);
rcvr_2 = new("reciever_2",2);
rcvr_3 = new("reciever_3",3);
sb = new();
gen = new();
gen2drv = new();
drv2sb = new();
rcv2sb = new();
endfunction
pkt.do_cfg(cfg);
// connect all the virtual interfacess.
drvr.intf = intf;
rcvr_0.intf = intf;
rcvr_1.intf = intf;
rcvr_2.intf = intf;
rcvr_3.intf = intf;
endfunction
task execute;
wait(sb.no_rcv_pkt == 10 );
terminate;
endtask
task terminate;
$swrite(msg,"\n\n Total number of packets sent %d, Total no of packet recieved
%d\n\n",sb.no_drv_pkt,sb.no_rcv_pkt);
avm_report_message("env",msg);
endtask
endclass
PACKET
CODE:packet.sv
`ifndef PKT_CLASS
`define PKT_CLASS
for (i = 0; i < this.len; i++) $write(psdisplay, "%s data[%0d] 0x%h \n", psdisplay,i, data[i]);
$write(psdisplay,"%s parity :0x%h \n",psdisplay,this.parity);
convert2string = psdisplay;
endfunction
this.da = to.da;
this.sa = to.sa;
this.len = to.len;
this.data = new[to.len];
foreach(data[i])
begin
this.data[i] = to.data[i];
end
this.parity = to.parity;
return;
endfunction
if (!$cast(cmp, to))
begin
avm_report_error("packet", "Attempting to compare to a non packet instance");
compare = 0;
diff = "Cannot compare non packets";
return compare;
end
if (to.sa != cmp.sa)
begin
$swrite(diff,"Different SA values: %b != %b", to.sa, cmp.sa);
compare = 0;
avm_report_error("packet",diff);
return compare;
end
if (to.len != cmp.len)
begin
$swrite(diff,"Different LEN values: %b != %b", to.len, cmp.len);
compare = 0;
avm_report_error("packet",diff);
return compare;
end
foreach(data[i])
if (to.data[i] != cmp.data[i])
begin
$swrite(diff,"Different data[%0d] values: 0x%h != 0x%h",i, to.data[i], cmp.data[i]);
compare = 0;
avm_report_error("packet",diff);
return compare;
end
if (to.parity != cmp.parity)
begin
$swrite(diff,"Different PARITY values: %b != %b", to.parity, cmp.parity);
compare = 0;
avm_report_error("packet",diff);
return compare;
end
return 1;
endfunction
endclass
`endif
PACKET GENERATOR
Use a class to build packet generator. Make an instance of this class. Packet generator generator
generates packets and sends to driver using gen2drv channel. gen2drv is used to connect the
packet generator and driver. Packet generator generates the packet and randomizes the packet.
Then the packet is put into gen2drv channel. Always check whether the randomization is
sucessful and display a message.
CODE: gen.sv
class generator extends avm_verification_component;
task run;
packet pkt;
if(!pkt.randomize())
avm_report_error("genarator"," randomiation failed\n");
else
avm_report_message("generator"," randomization done\n");
put_port.put(pkt);
end
endtask
endclass
CONFIGURATION
CODE:CFG.SV
`ifndef CFG_CLASS
`define CFG_CLASS
class Configuration extends avm_verification_component;
rand bit [7:0] da_port [4];
string msg;
task run;
endtask
endclass
`endif
DRIVER
tlm_blocking_get_if#(packet) get_port;
tlm_blocking_put_if#(packet) put_sb;
super.new(nm);
endfunction
endfunction
task run;
reset_dut();
cfg_dut();
forever
begin
get_port.get(pkt);
$display("consumer: sendging %s packet\n", pkt.convert2string);
drive(pkt);
avm_report_message("Driver","Puting packet to score board");
put_sb.put(pkt);
@(negedge intf.clock);
end
endtask
task drive(packet pkt);
logic [7:0] pack[];
int pkt_len;
pkt_len = pkt.byte_pack(pack,0,0);
$swrite(this.msg,"Packed packet length %d \n",pkt_len);
avm_report_message("Driver",this.msg);
@(negedge intf.clock);
for (int i=0;i< pkt_len - 1;i++)
begin
@(negedge intf.clock);
intf.data_status <= 1 ;
intf.data_in <= pack[i];
end
@(negedge intf.clock);
intf.data_status <= 0 ;
intf.data_in <= pack[pkt_len -1];
@(negedge intf.clock);
endtask
task reset_dut();
avm_report_message("reset_dut"," Starting... reset_dut \n");
@(negedge intf.clock);
avm_report_message("reset_dut"," Starting... reset_dut \n");
intf.data_status <= 0;
intf.data_in <= 0;
intf.read <= 0;
intf.mem_data <= 0;
intf.mem_add <= 0;
intf.reset <= 0;
intf.mem_en <= 0;
intf.mem_rd_wr <= 0;
@(negedge intf.clock);
#2 intf.reset <= 1;
@(negedge intf.clock);
#2 intf.reset <= 0;
@(negedge intf.clock);
@(negedge intf.clock);
avm_report_message("reset_dut"," Ending... reset_dut \n");
endtask
task cfg_dut() ;
avm_report_message("cfg_dut"," Starting... cfg_dut \n");
for(int i = 0;i<4 ;i++)
begin
intf.mem_en <= 1;
@(negedge intf.clock);
intf.mem_rd_wr <= 1;
@(negedge intf.clock);
intf.mem_add <= i;
intf.mem_data <= cfg.da_port[i];
end
@(negedge intf.clock);
intf.mem_en <= 0;
intf.mem_rd_wr <= 0;
intf.mem_add <= 0;
intf.mem_data <= 0;
endtask
endclass
RECIEVER
Reciever is a tranctor. Start the collection of packet from dut in the run() task and send them to
score_board through rcv2sb channel.
CODE:reciever.sv
task run;
byte received_bytes[$] ;
packet rcv_pkt,pkt;
pkt = new();
forever
begin
@(posedge (intf.ready[port]));
while (intf.ready[port]) begin
intf.read <= 4'b0001 << port;
@(negedge intf.clock);
received_bytes.push_back(intf.data_out[port]);
end
intf.read <= 4'h0;
pkt.unpack(received_bytes);
received_bytes = {};
rcv_pkt = new pkt;
put_sb.put(rcv_pkt);
end
endtask
endclass
SCOREBOARD
When the packet comes from reciever, the scoreboard gets the expected packet from drv2sb
channel and compare them.
CODE:scoreboard.sv
`ifndef SB_CLASS
`define SB_CLASS
integer no_drv_pkt;
integer no_rcv_pkt;
string msg;
tlm_blocking_get_if#(packet) drv_port;
tlm_blocking_get_if#(packet) rcv_port;
function new( );
super.new("Scoreboard");
no_drv_pkt = 0;
no_rcv_pkt = 0;
avm_report_message("sb","Scoreboard created");
endfunction
task run();
end
join_none
forever
begin
rcv_port.get(rcv_pkt);
this.no_rcv_pkt++;
exp_pkt = exp_que.pop_back();
end
endtask
endclass
`endif
VMM Ethernet
This testbench is developed in VMM (Systemverilog) for the Ethernet core available from
opencores.org. My intension here is to explore the VMM methodology but not to verify the
Ethernet core, as a result there are many bugs in the environment. I dont remember the versions
of VMM but I developed these in the third quarter of 2007. To simulate this testbench some
dependencies on libraries has to be removed from RTL files. It takes bit time for these changes in
RTL.
Feauters:
Full support of automatic random, constrained random, and directed testcase creation.
Supports injuction of random errored packets.
Supports 1G Fullduplex modeled both in RX and TX paths.
Protocol Checker/Monitor for self checking.
Built in function coverage support for packets.
Developed in Systemverilog using Synopsys VMM base classes.