Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Couse Agenda:
Introduction Threads
Data Types Mailbox, Semaphore
Struct and Enums Events
Memories (Arrays)
Tasks and Functions Build env components Generator
Interfaces Driver(BFM)
Clocking Block Monitor
System Verilog Statified event queue Reference Model
OOPs Basics Scoreboard
Advanced OOP Environment
TB Architecture Package
Reusable TB Environment Basic Test case
Virtual Methods Top level module
Randomization Functional coverage
Constraints Scoreboard and Coverage model
Regression Test case
2
Introduction:
3
System Verilog includes a set of extensions to the Verilog HDL to help
engineers design and verify larger and more complex designs. In fact, many
industry watchers consider it the first Hardware Description and Verification
Language (HDVL), because it combines VHDL and Verilog features with
those of Hardware Verification Languages (HVLs) Vera and e, as well as C
and C++.
HDL vs HVL
Static consumes memory
New Data types – String, 2-state Data types, logic, integer,
ultidimensional Arrays, Enumeration, Structures and Unions,
Interfaces
OOPs and Randomization
Assertions, Coverage, Mailboxes, Semaphores
Constrained Random Verification Environment
A set of extensions to Verilog-2001.
Randomization – constraints
Functional Coverage
Reusability – Reuse without breaking the already working code.
A language which supports Object Oriented Programming. C & C++ - Data types
and OOP.
PSL – Assertion Based Verification
VHDL – enum, Packages
Importance of Verification:
◦ A primary purpose for verification is to detect failures
so that bugs can be identified and corrected before it gets shipped to costumer.
Hardware Designs get more and more complex
Hand-written stimulus (directed test) is difficult to write and maintain
Corner cases are difficult to catch
Visual inspection of waveforms in order to trace a bug is a tedious task
High cost of faulty designs (loss of life, product recall)
“The amount of time spent on verification now exceeds the
amount of time spent on design, comprising up to 70 percent of the
total development effort.”
High cost of design debug
4
Designers (sometimes verification engineers = 2 * design engineers)
Time-to-market
5
System Verilog VHDL:
A set of extensions to Veriolg-2001
Randomization – constraints
Functional Coverage
Reusability – Reuse without breaking the already working code.
A language which supports Object Oriented Programming.
Language Evolution:
C & C++ - Data types and OOP
PSL – Assertion Based Verification
VHDL – enum, packages
2005- Synopsys donated Openvera Language(Verification
Functionality)
VERA – Randomization and Constraints
2002 – SV created from Superlog Language(DPI)
2009- adopted as IEEE 1800-2009 standard by merging with base Verilog
(IEEE 1364-2005)
2012 – IEEE 1800-2012 Standard Revision (current version)
Data Types:
In this module, you will:
Review Verilog 2001 data types
Learn how to use System Verilog data types and packages
See and discuss examples of the data types
void = function_call();
types. There are different ways to define user defined data types. They
are :
Class
Enumerations
Struct
Union
Typedef
Enumeration/Enum Data Type
first() -- > returns the value of the first member of the enumeration.
last() -- > returns the value of the last member of the enumeration.
next(N) -- > returns the value of next Nth member of the enumeration.
prev(N) -- > returns the value of previous Nth member of the enumeration.
num() -- > returns the number of elements in the given enumeration.
name() -- > returns the string representation of the given enumeration value.
In the following example, colors is defined to be variable of the unnamed
enumerated int type that includes
the members red, green, blue, yellow, white, black. enum { red, green,
blue, yellow, white, black } Colors;
Example:
module enum_datatype;
//declaration
enum { red, green, blue, yellow, white, black }
Colors; //display members of Colors
initial begin
Colors = Colors.first;
for(int i=0;i<6;i++) begin
$display("Colors :: Value of %0s \t is = %0d",Colors.name,Colors);
Colors = Colors.next;
end
end
endmodule
Simulator Output
Colors :: Value of red is = 0
Colors :: Value of green is = 1
Colors :: Value of blue is = 2
module enum_datatype;
//declaration
typedef enum { red=0, green, blue=4, yellow, white=10, black } colors;
enum { a, b, c, d, e, f, g } alphabets;
colors first_set;
colors second_set;
initial begin
first_set = first_set.first();
$display("first_set first color is \t %0s, \t Value = %0d", first_set.name(),first_set);
first_set = first_set.last();
$display("first_set last color is \t %0s, \t Value = %0d", first_set.name(),first_set);
second_set = first_set;
$display("second_set color is \t %0s, \t
Value = %0d",
second_set.name(),second_set);
second_set =second_set.prev(2);
endtask
endpackage
----------------------------
include "msgPkg.sv“
import msgPkg::*;
module simple_package();
initial begin
msgPkg::initMsgPkg("PACKAGES",0);
end
endmodule
Memories (Arrays)
Arrays:
A collection of similar data items that can be
selected by indices computed at run-time.
Various usage scenarios with respect to
initializing, accessing the members and usage of physical memory.
Verilog Array:
Verilog arrays support single dimension, multi-dimension arrays
-----------------------------
Examples:
reg [7:0] address; --- one dimension array containing one element
reg [7:0] memory [0:255]; -- One dimension array containing 256 elements
reg [15:0] array [0:255] [0:255]; -- Two dimensional array with 256
elements in two directions and width 16.
SV Packed Array:
Packed Array Represents a Contiguous set of bits.
It is treated as a Single Vector.
It allows only bit, logic and reg type declaration.
It does not allow Integer, Int, Byte type ofdeclaration.
Examples:
logic [2:0] pack_array ;
logic [1:0][2:3] pack_array_2d ;
Logic [3:0] [7:0] data_reg ;
array[0][1] = 4433
array[1][0] = 6655
array[1][1] = 8877
array[0][0][0] = 11
array[0][0][1] = 22
array[0][1][0] = 33
array[0][1][1] = 44
array[1][0][0] = 55
array[1][0][1] = 66
array[1][1][0] = 77
array[1][1][1] = 88
array[0][0][0][0] = 1
array[0][0][1][1] = 1
Types of Arrays:
Hold a fixed number of equally-sized data elements
Individual elements are accessed by index
Types :
Static Array
Fixed size
Dynamic array
Size is given in run time
Associative Array
Sparse Matrix Array
Fixed Size Array:
◦ Size should be given during compilation time.
◦ Types:
Packed,
Unpacked and
Multidimentional
Dynamic Array:
Dynamic Array is 1 – dimensional Unpacked Array
Use a dynamic array when the array size must change during the
simulation.
Example :
Data_type array_name [] ;
bit [3:0] nibble[]; // Dynamic array of 4-bit vectors integer mem[];
// Dynamic array of integers
Dynamic Array Methods:
new() allocates the storage and initializes the newly allocated
endmodule
Results
Associative Array:
Use associative arrays when the data space is unbounded or sparsely
populated.
Declare the array by specifying a type instead of a size for its one dimension.
Accessed with integer, or string index
Array elements do not exist until you assign to the
Array elements are “pairs” of associated key and data values.
Example :
bit [3:0] aa1[int] ; // Associative array of 4-bit 2-state index type int.
logic [7:0] aa2 [integer] ; // Associative array of 8-bit logic index type integer.
integer i_array [*] ; /// Associative array of integer (wildcard index type).
num() Returns the number of entries in an associative array
Example :
module integer_associative_array ();
integer as_mem [integer];
integer i;
initial begin
/ A
d
d
e
l
e
m
e
n
t
a
r
r
a
y
a
s
_
m
e
m
[
1
0
0
]
=
1
0
1
;
$di
sp
la
y
("
va
lu
e
st
or
ed
in
1
0
0
is
%
d"
,
1
0
1)
;
as
_
m
e
m
[1
]
=
1
0
0;
$di
sp
la
y
("
va
lu
e
st
or
ed
in
1
is
%
d"
,
1
0
0)
;
as
_
m
e
m
[5
0]
=
9
9;
$
d
i
s
p
l
a
y
(
"
v
a
l
u
e
s
t
o
r
e
d
i
n
5
0
i
s
%
d
"
,
9
9
)
;
a
s
_
m
e
m
[
2
5
6
]
=
7
7
;
$display ("value stored in 256 is %d", 77);
/ Print the size of array
/ $display ("size of array is %d", as_mem.num());
s
t
o
r
e
d
i
n
f
i
r
s
t
i
n
d
e
x
i
f
(
a
s
_
m
e
m
.
f
i
r
s
t
(
i
)
)
begin
$display
("value at
first index
%d value
%d", i,
as_mem[i
]); end
t
h
e
f
i
r
s
t
i
n
d
e
x
a
s
_
m
e
m
.
d
e
l
e
t
e
(
1
0
0
)
;
d
i
s
p
l
a
y
(
"
D
e
l
e
t
e
d
i
n
d
e
x
1
0
0
"
)
;
V
a
l
u
e
s
t
o
r
e
d
i
n
f
i
r
s
t
i
n
d
e
x
if (as_mem.first(i))
begin
$display ("value at first index %d value %d", i, as_mem[i]);
end
#1 $finish;
end
endmodule
Results:
value stored in 100 is 101
value stored in 1 is 100
value stored in 50 is 99
value stored in 256 is 77
size of array is 4
index 100 exists 1
value at first index 1 value 100
value at last index 256 value 77
Deleted index 100
value at first index 1 value 100
Queues:
Use a Queue “array” where insertion and extraction order
are important.
Declare a Queue by using $ as the size of its onedimension:
Example :
int q_int [ $ ];
int q_int [ $: 200 ]; // Optionally limit the queue size.
module queue_data();
/
Q
u
e
u
e
i
s
d
e
c
l
a
t
e
d
w
it
h
$
i
n
a
r
r
a
y
s
i
z
e
i
n
t
e
g
e
r
q
u
e
u
e
[
$
]
=
{
0
,
1
,
2
,
3
,
4
}
;
i
n
t
e
g
e
r
i
;
initial begin
$
d
i
s
p
l
a
y
(
"
I
n
i
t
i
a
l
v
a
l
u
e
o
f
q
u
e
u
e
"
)
;
p
r
i
n
t
_
q
u
e
u
e
;
/ I
n
s
e
r
t
n
e
w
e
l
e
m
e
n
t
a
t
b
e
g
i
n
o
f
q
u
e
u
e
q
u
e
u
e
{
5
,
q
u
e
u
e
}
;
/
$display
("new
element
added
using
concate"
);
print_qu
eue;
/
I
n
s
e
r
t
u
s
i
n
g
m
e
t
h
o
d
a
t
b
e
g
i
n
i
n
g
q
u
e
u
e
.
p
u
s
h
_
f
r
o
n
t
(
6
)
;
$display
("new
element
added using
push_front")
;
print_queue
;
/ Insert using method at end
queue.push_back(7);
$display ("new element added using push_back");
print_queue;
/ Using insert to insert, here 4 is index
/ and 8 is value
queue.insert(4,8);
$display ("new element added using insert(index,value)");
print_queue;
// get first queue element method at beginning
i = queue.pop_front();
$display ("element poped using pop_front"); print_queue;
/ get last queue
element method at
end i =
queue.pop_back()
;
$display ("element
poped using
pop_end");
print_queue;
/ Use delete method to delete element at index 4 in queue queue.delete(4);
/ $display ("deleted element at index 4");
p
r
i
n
t
_
q
u
e
u
e
;
#
1
$
f
i
n
i
s
h
;
E
n
d
t
a
s
k
p
r
i
n
t
_
q
u
e
u
e
;
i
n
t
e
g
e
r
i
;
$
w
r
i
t
e
(
"
Q
u
e
u
e
c
o
n
t
a
i
n
s
"
)
;
f
o
r
(
i
=
0
;
i
<
q
u
e
u
e
.
s
i
z
e
(
)
;
i
+
+
)
b
e
g
i
n
$
w
r
i
t
e
(
"
%
g
"
,
q
u
e
u
e
[
i
]
)
;
e
n
d
$write("\n"); endtask endmodule
Results:
Initial value of queue
Queue contains 0 1 2 3 4
new element added using concate
Queue contains 5 0 1 2 3 4
new element added
using push_front
Queue contains 6 5 0
1234
new element added
using push_back
Queue contains 6 5
012347
new element added using insert(index,value)
Queue contains 6 5 0 1 8 2 3 4 7 element poped using pop_front
Queue contains 5 0 1 8 2 3 4 7 element poped using pop_end
Queue contains 5 0 1 8 2 3 4 deleted element at index 4
Queue contains 5 0 1 8 3 4
Queue Indexing:
Example :
module another_array();
int q_int[$] ; // Queue declaration syntax
int data ;
initial
begin
q_int = {0, q_int} ;// { 0 }
q_int = {q_int, 1} ; // { 0, 1 }
q_int = {2, q_int} ; // { 2, 0, 1 }
q_int = q_int[0:$-1] ;
$display("------------------") ;
foreach(q_int[i])
begin
$display("Value of Q_INT = %d", q_int[i]) ;
end
end
Endmodule
Tasks and Functions
Agenda:
Understanding of Tasks & Functions
Differences between tasks and functions
Features of System Verilog tasks and functions
Conclusion
List features of Functions in Verilog:
Functions will have only 1 return value
Functions cant call tasks
Functions are zero time.\Functions need begin..end to define the functionality in it
Applications
Computations calculations
List Features of Tasks in Verilog:
Tasks can have many variables.
Tasks can call tasks or functions.
Tasks can be either zero simulation time or can have wait statements.
Applications
BFM creation
Verilog Tasks and Functions:
Enhancements in Sv:
System Verilog adds the following enhancements to verilog 2001 tasks and
funtions :
Multiple statements in task/function don’t require begin/end block.
Function output and inout ports.
Functions can return a “void” type.
Returning from a task/function before reaching the end
(return).
Passing arguments by reference (ref) and by value.
Task/Function arguments passed by name instead of order.
Default task and function arguments values are allowed
Multiple statements in a task or function without requiring a begin...end or
fork...join block.
Function is having output and inout ports along with input ports.
Returning from a task or function before reaching the end of the task or function.
return;
end
/ Code for the rest of the task
...
endtask
//return
statement in
function
function bit
transmit(...);
// Send transaction
System Verilog Functions:
Functions will return many variables
Functions can call tasks by using fork..join_none
Functions are zero time.simulation
Functions support call by value and call by reference.
endtask :task_name
Interfaces Definition:
What is an Interface?
Named bundle of nets and variables
Enables communication between blocks
Signals can be referenced by interface
Encapsulates functionality as well as connectivity
Enables high level of re-usability
Interfaces is the window between the DUT & BFM
CLK
BFM
BUS DUT
Complex Signals:
SV Design
• Hard to add signals
• Error prone
Communication
encapsulated:
Reduces error, easier
Interface Syntax: to
modifySignificant
code reduction–
savestime
Syntax: interface i_name [(interface_ports)];
[parameters]
interface_definitions;
endinterface [: i_name]
interface mem_bus (clk); parameter dbus=32,abus=8; input clk;
wire direction;
logic sel,enb,write;
logic [ADDR_WIDTH-1:0] addr; logic [DATA_WIDTH-1:0]dout;
logic [DATA_WIDTH-1:0] din;
endinterface
// DUT Module
module dut (input clk, apb_if bus);
endmodule
// BFM Module
module bfm (input clk, apb_if bus);
endmodule
// TOP Module
module top;
logic clk ;
apb_if #(64,32) bus(); // Instantiate interface
//Overwrite parameters
bfm u_bfm (clk, bus); // Pass interface as
/ reference
dut u_dut (clk, bus); // Pass interface
// reference
OR
bfm u_bfm (.*); // If the signals are of the // same name
OR
dut u_dut (.clk(clk), // If the design has port list
.sel(bus.sel),
………..);
end module
if(bus.write)
begin
bus.dout = data;
bus.addr = addr ;
end
endmodule
Concepts of Modports:
Specifies direction for interface signals
Reduces error from Verification Environment as well as DUT
Provides error when direction is violated
interface apb_if;
logic sel,enb,write;
logic [11:0] addr;
logic [31:0]dout;
logic [31:0] din
CLOCKING BLOCK
Input Skew– TB samples DUT outputs before the clock edge.
One clock cycle delay from DUT output to TB input.
Output Skew – TB drives the DUT inputs at the clock edge.
input output
skew skew
in1;
…
endclocking
modport DUT(input rst, in1,in2,mode, output dout, rslt);
modport TB(clocking cb);
endinterface: dut_intf
program automatic test(dut_intf.TB rtr_io);
Initial rtr_io.cb.rst <=
1’b0; rtr_io.cb.in1 <= ’1;
endprogram: test
module dut( dut_intf.DUT intf); …
endmodule: dut
interface dut_intf(input bit clock); logic rst ; logic [3:0] in1 ; logic [3:0] in2 ; Mode type mode; logic
[7:0] rslt ; clocking cb @(posedge clock); default input #1 output #0; output rst; output in1; output
in2;
out
pu
t
m
od
e;
in
pu
t
rsl
t;
endclocking modport
TB(clocking cb);
endinterface
Parameterized Skew Value
Example :
module my_testbench;
default clocking clock1 @(posedge cp1); input a1, a2; output b1;
endclocking
endclocking
##5 b2 = a2;
endmodule
OOPS Basics:
OOP breaks a Testbench into blocks that work together to accomplish the verification
goal
Why Oops:
• Highly abstract system level modeling
• Classes are intended for verification
• Classes are easily reused and extended
• Data security
• Classes are dynamic in nature
• Easy debugging, one class at a time
Functions,
Behavioural
blocks
(always, initial)
Module vs Class:
Why use class ?
Overview of Classes:
System Verilog provides an object-oriented class data abstraction.
Objects can be dynamically created, deleted, assigned and accessed by object handles
Object handles provide a pointer-like mechanism
Objects are dynamic, modules are static
Objects are created and destroyed as needed
Instances of classes are objects
A handle points to an object (class instance)
Object handles can be passed as arguments
Object memory can be copied or compared
Instances of modules cannot be passed, copied or compared
Classes can be inherited, modules can not
Classes can be modified via inheritance without impacting existing users Modifications to
modules will impact all existing users
Class objects are created with the new method(function).
Every class has a default constructor new method.
Constructor – new function Allocates memory.
Returns the address of the memory to handle.
Initializes all the properties with default values.
OOP objects are created from class definitions:
Similar to instance creation from module definition
Object memory is constructed by calling new()
Handle used to refer to object
class Driver; bit[3:0] sa, da; byte payload[]; task send();
...
endclass: Driver
Driver drvr1 = new(); Driver drvr2,
drvr3; initial begin drvr3 = new();
...
end
Object memory is created via a call to new()
Object members are accessed via the object handle:
OOPs Concepts:
Class Encapsulation
Object Abstraction
Inheritance
Polymorphism
Class Assignment Example:
Object will be created only after doing new to an
class handle,
packet pkt_1;
pkt_1 = new();
packet pkt_2;
pkt_2 = pkt_1;
Here object is created only for pkt_1, pkt_2 is just an handle
to the packet.
pkt_1 is assigned to the pkt_2.
so only one object has been created, pkt_1 and
pkt_2 are two handles both are pointing to the same object.as both the
handles are pointing to the same object any changes made with-respect to
pkt_1 will reflect on pkt_2 .
Example-Class Constructor:
Null-Object Handle
Accessing members:
this Keyword:
this keyword
An object’s handle to itself Unambiguously refers to non-static class properties and
methods of the current instance (object).
More readable – allows method arguments to have same name as class variables.
module automatic test;
class Packet;
bit[3:0] sa, da;
bit[7:0] payload[];
function new(bit[3:0] sa, da, int payload_size);
this.sa = sa;
this.da = da;
this.payload = new[payload_size];
endfunction: new
endclass: Packet …
endmodule: test
Creating an Object:
OOP objects are created from class definitions:
Similar to instance creation from module definition
Object memory is constructed by calling new()
Handle used to refer to object
Objects:
Object Assignment:
Copying an Object:
Shallow Copy:
Shallow copy – only properties of parent class are copied, not the objects of
subclass
Shallow copy allocates the memory, copies the variable values and returns the
memory handle.
In shallow copy All of the variables are copied across: integers, strings,
instance handles, etc
Note: Objects will not be copied, only their handles will be copied.
To perform full or deep copy, custom method can be added.
Example:
Object will be created only after doing new to an class handle,
packet pkt_1;
pkt_1 = new();
packet pkt_2;
pkt_2 = new pkt_1;
In the last statement pkt_2 is created and class properties were copied
from pkt_1 to pkt_2. this is called as "shallow copy".
Deep Copy:
Deep copy – both data and objects in the parent class are copied.
User defined copy method should be used.
In shallow copy, Objects will not be copied, only their handles will be copied.
To perform full or deep copy, custom method can be added.
In the custom method new object is created and all the class properties will be copied to
new handle and new handle will be returned
Advanced Oops:
Inheritance:
New classes can be created based on existing classes.
A derived class by default inherits the properties and methods of its parent or
base class.
base class or parent class -> existing class.
derived class or child class -> New class.
The derived class may add new properties and methods, or modify the inherited
properties and methods.
Reusability – existing classes from previous project.
◦ Accelerates verification
◦ TB uses existing classes which have been used extensively and validated
– less Debug.
TB methodologies – OVM, UVM, VMM
◦ Base class library – Common code
◦ Customization/Implementation – derived classes defined by user.
New versions of TB components
◦ Driver injects errors into DUV.
◦ -----------------------------------------
Parent class properties is accessed using child class handle, i.e child class will
have(inherit) parent class properties and methods
class parent_class;
bit [31:0] addr;
endclass
class child_class extends parent_class;
bit [31:0] data;
endclass
module inheritence;
initial begin
child_class c = new();
c.addr = 10;
c.data = 20;
endmodule
Simulator Output
Example:
class parent_class;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
function display();
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
initial begin
child_class c=new();
c.addr = 10;
c.data = 20;
c.display();
end
endmodule
Simulator Output
Data = 20
Super:
The super keyword is used in a derived class to refer to members of the
parent class.
It is necessary to use super to access members of a parent class when those
members are overridden by the derived class.
In child class, method with the same name of parent class will override the
method.
By using super keyword parent class method can be accessed from child
class.
Example:
Parent class method display is overridden in the child class, by calling super.display()
from child class display method of parent class can be accessed.
class parent_class; bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
super.display();
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
c.data = 20;
c.display(); end
endmodule
Data = 20
Super.new:
The super keyword is used within a derived class to refer to overridden
members of parent class as shown in this example:
class Base1;
int var;
function new(input int var); // Has argument
this.var = var;
endfunction
endclass
class Extended extends Base1;
function new(input int var); // Needs
argument
super.new(var); // Must be first
line of new
// Other
constructor
actions
endfunction
endclass
Passing by Ref:
$Cast:
By default all the members and methods
of a class are accessible from
anywhere using the object handle,
sometimes this could corrupt the class
members values, which should not be
touched at all.
local
protected
local:-
local
protected
local:-
by declaring
members as local.
Any violation could result in compilation
error.
Syntax:
local integer x;
p_c.
displ
ay();
end
class parent_class; endmodule
local bit [31:0] tmp_addr; Simulator Output
function new(bit [31:0] r_addr); Error- Illegal class variable access
tmp_addr = r_addr + 10; testbench.sv,
endfunction
function display();
$display("tmp_addr =
%0d",tmp_addr);
endfunction
Local
endclass member
'tmp_addr' of
// module
class
module encapsulation; 'parent_class'
is not visible
initial begin to scope
'encapsulatio
parent_class p_c = new(5); n'.
p_c.t Example 2:Accesing local variable
mp_ within the class(Allowed)
addr
= class parent_class;
20; //
Acce local bit [31:0] tmp_addr;
ssing
function new(bit [31:0] r_addr);
local
varia tmp_addr = r_addr + 10;
ble
outsi endfunction
de
the function display();
class
$display("tmp_addr = parent_class p_c = new(5);
%0d",tmp_addr);
p_c.display();
endfunction
end
endclass
endmodule
// module
Simulator Output
module encapsulation;
Addr = 5
initial begin
protected :-
In some use cases it is requires to access the class members only by the
derived class's, this can be done by prefixing the class members with
protected keyword.
Any violation could result in compilation error.
Syntax:
protected integer x;
Example 1: Accessing protected variable outside the class(Not Allowed)
class parent_class;
protected bit [31:0] tmp_addr;
function display();
$display("tmp_addr = %0d",tmp_addr);
endfunction
endclass
// module
module encapsulation;
initial begin
parent_class p_c = new(5);
child_class c_c = new(10);
/ variable declared as protected cannot be
accessed outside the class p_c.tmp_addr = 10;
p_c.display();
c_c.incr_addr(); //Accessing protected
variable in extended class c_c.display();
end
endmodule
Simulator Output
Error- Illegal class variable access
testbench.sv,
function display();
$display("tmp_addr =
%0d",tmp_addr); endfunction
endclass
class child_class extends
parent_class; function new(bit
[31:0] r_addr);
super.new(r_addr);
endfunction
// module
module encapsulation;
initial begin
child_class c_c = new(10);
c_c.incr_addr(); //Accessing protected variable in extended
class c_c.display();
end
endmodule
Simulator Output
Addr = 21
Virtual Classes:
Create code that can be shared across
multiple projects.
Build test benches that have a common look and
extended
feel. from virtual class can
Two constructs that help build a be instantiated only if all virtual
shareable base class methods have bodies.
Virtual class – can be extended but not Pure virtual methods can be
instantiated directly.
Pure virtual methods – declared only in abstract class .
prototype without a body.A class
Randomization
Randomization and its Advantages:
• Randomization – is a method of
• Generating Stimuli for the Purpose of Verification of a
DUT
Constrain the values of the random variables
To create legal stimulus.
To explore areas of interest.
To conditionally switch between modes of operation.
Injecting Randomness Exposes Corner Cases
Run longer simulations with more stimulus
A directed test finds the bugs you expect to be in the design, While a random test
can find bugs you never anticipated.
O The last few bugs may only be found with directed tests, but the vast majority
of bugs will be found with random test.
www.technospirit.net
Post_randomize is called after objects are randomized.
Pre/post_randomize can be overridden. function void pre_randomize(); function void
post_randomize();
class transaction; rand bit [0:7] data; rand bit [0:1] ch; function void post_randomize;
$display(“data is %b”, data);
$display(“channel is %b”,
ch); endfunction initial begin int success;
transaction t_h = new(); success =
t_h.randomize();
Random Variables.
Class variables can be declared random using the rand and randc type
modifier keywords.
Arrays, Dynamic & Associative can be declared rand/randc, in which case all
of their member elements are treated as rand/randc.
The size of a dynamic array declared as rand/randc can also be constrained. Then, the
array shall be resized according to the size constraint, and then all the array elements shall
be randomized
Random Modifiers:
Rand modifier
Variables declared with the rand keyword are standard random variables.
Example:
rand bit [7:0] y;
Randc modifier
These are random-cyclic variables that cycle through all the values
in a random permutation of their declared range.
Random-cyclic variables can only be of type bit or enumerated types, and
can be limited to a maximum size.
Example:
randc bit [1:0] y;
When the iteration finishes, a new iteration automatically
starts.
Randc variable is solved before rand
Constraint Blocks
The values of random variables are determined using constraint expressions that
are declared using constraint blocks.
Operators with side effects, such as ++ and – - are not
allowed.
Randc variables cannot be specified in ordering constraints( solve …
before).
Constraint block names must be unique within a class.
class A;
r
Constraints follow the same general rules
for inheritance as class
variables, task, and functions.
A constraint in a derived class that
uses the same name as a constraint
in its parent classes overrides the
base class constraints.
Example:
class A;
;
c
o
Constraints support integer value sets and the set
n
membership operator.
s
Absent any other constraints,
t all values have an
equal probability of being
r
n
rand integer
t
x, y, z;
constraint c1
c
{ x inside { 3, 5, [9 :
{ 15],
[24 : 32],
x
[y : 2*y ],
< z };
}
0
;
rand integer a, b, c;
constraint
} c2 { a inside
{n b, c }; }
e
d
c
integer fives[0:3]
l
a
= { 5,s
s
10, 15,
20 c};
l
rand
a
integer
Example: v; s
s
constraint
mode == smallc3 {B v< 10;
-> len mode == large -> len >
100; inside
fives;e }
x
Constraints support sets of weighted values called
t
Distributions. e
If no weight is specified
n for an item, the
default weight is :=
d 1
s
The := operator assigns the specified weight to the
item, or if the item is a
A
range, to every value in the range.
;
Example 1:
x != 200;
s
t
x dist
r {100 := 1, 200 := 2,
300 a:= 5}
i
n
Example 2:
t
x dist { [100:102] := 1,
c
{
If...else style constraint declarations are
Limitations - A dist operation shall not be applied to
equivalent to implications.
randc variables x
>
Example:
0
i
;
f
}
e
n
( d
c
l
m
a
s
o s
m
l
Example:
l
class C;
)
rand byte A[] ;
Endclass
l
When an object member of a class is
declared
e rand, all of its constraints and
randomn
variables are randomized
simultaneously
along with the other class variables and
constraints.
<
e
class B;
i
rand bit s;
f
rand bit [31:0] d; constraint c { s -> d
== 0;
}
(
constraint order { solve s
beforemd; }
endclasso
d
)
A constraint block can be defined as static
by including the static
l
keyword in its definition.
e
If a constraint block is declared as static,
n to constraint_mode() shall affect
then calls
all instances of the specified constraint in
all objects.
>
Thus, if a static constraint is set to OFF, it
is off for all instances of
1
that particular class.
0
Syntax :
0
constraint_declaration
; ::= [ static ]
constraint
constraint_identifier
constraint_block
To handle these common cases, System Verilog allows
constraint expressions to include function calls, but it
imposes certain semantic restrictions.
Functions that appear in constraint expressions cannot
contain output or ref
arguments.
Functions that appear in constraint expressions should
be automatic.
Functions that appear in constraints cannot modify
the constraints, for example, calling rand_mode or
constraint_mode methods.
Circular dependencies created by the implicit variable
ordering shall result in an error. Within each
prioritised set of constraints, cyclical (randc) variables
are solved first.
Example:
endfunction
constraint C1 { length =
= count_ones( v ) ; }
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.
r
a
n
d
i
n
t
n
;
r
a
randomize()
n
Variablesdin an object are
randomized using the
S
randomize() class method.
L
Every class
i has a built-in
randomize()
s virtual method,
declared
t as:
virtual function int randomize();
n
The randomize()
e
method is a virtual
function that
x generates random
values fort all the active random
variables ; in the object, subject to the
active constraints.
c The randomize()
methodonreturns 1 if it successfully
str
sets all the random variables and
ai
objects to valid values
nt
here. endfunction
so
rt
{n
<
ne
xt.
By using the randomize()...with construct,
n;
users can declare in-line constraints at the
}
point where the randomize() method is
en
called. These additional constraints are
dc
applied along
la with the object constraints.
Example:
ss
class SimpleSum
c
on
rand
strbit [7:0] x, y, z;
ai
constraint
nt c {z == x + y;}
so
endclass
rt
{
Task
if(
n
InlineConstraintDemo(Si
e
mpleSum
x t p);
!=
int success;
nu
ll )
success =
n
<
p.randomize(
n
ex
) with {x <
t.
n;
y;}; endtask
}
The rand_mode() method can be used to
control whether a
random variable is active or inactive.
task
object[.random_variable]::rand
_mode(bit on_off);
or
function int
object.rando
m_variable::r
and_mode();
The constraint_mode() method can be used to
control whether a constraint is active or
inactive. When a constraint is inactive, it is not
considered by the randomize() method. All
constraints are initially active.
The constraint_mode() method is built-in and
cannot be overridden.
Example:
function integer toggle_rand( Packet p );
.
c
The randomize()
o method can be used to
temporarily
n control the set of random and
s
state variables within a class instance or
t
object.
r
When athe randomize method is called with no
arguments,
i it assigns new values to all random
variables
n in an object—those declared as rand
or randc
t —such that all of the constraints are
satisfied.
_
m
All other variables in the object are
o
considered state variables.
d
f
i
l
The scope randomize function,
t
std::randomize(), randomizes data in the
e scope, without the need to define a
current
class ror instantiate a class object.
1
The scope randomize function behaves
.
exactly the same as a class randomize
c
method, except that it operates on the
o
variables of the current scope instead of
class nmember variables.
s
Example:
t
module stim;
r
bit [15:0] addr;
b a
i
t i
[
n
3
t
1
: _
0
] m
d
o
a
d
t
a e
;
(
f
0
u
n
c ;
t
i srandom() - Allows manually seeding the Random Number Generator
(RNG) of objects or threads.
Syntax o:
n void srandom( int seed );
function
e
The keyword randcase introduces a case
b
i l
statement
t that randomly
s
selects
g one of its branches.
e
e
n
_
s
t
i p
m
(
.
)
f
;
bi
i
tl
t
s
ue
c
cr
e
s1
s
,
.
c
r
do
_
wn
r
;s
st
r
c
ca
randsequence block.
e
i
s
s
n
=t
_
r
am
no
d
d
o
me
i
(
z
1
e
()
a
;
d
d
r
,
t
d
ao
t
g
a
g
,
rl
d
e
_
w_
rr
;
n
If … else Production Statements - A production
rd
can bee made conditional by means of an if...else
production
t statement.
=
Example:
u
r
randsequence()
n
p ...
P
r.
P
d
r _
_
P
wa
O
rn
;
d :
en
df o
un i
cti m f
on
...
i
(
endm z
odule
e d
e
$urandom_range()
( - Returns an unsigned
p
integer within a specified range.
) t
Syntax : hfunction int unsigned $urandom_range( int
;
unsigned maxval, int unsigned minval = 0 );
e
Example: <
val =n$urandom_range(7,0);
d 2
Example:
f
u val = $urandom_range(0,7);
)
n
t U
i S
Interleaving
o Productions - rand join
H
n
The rand join production control is used to
randomlye interleave two or more production
l
sequences while maintaining the relative order
s
of each sequence.
e
Example: P
O
randsequence( TOP )
P
TOP : rand join S1 S2 ;
S1 : A B ;
;
S2 : C D ;
endsequence
P
The generator will randomly produce the following
U
sequences:
S
H
ABCD
ACBD
ACDB :
CDAB
CABD
CADB {
;
d
o
Aborting Productions - break & return
_
The break statement terminates the sequence generation.
p
Example:
u
randsequence()
s
WRITE : SETUP DATA ;
h
SETUP : { if( fifo_length >= max_length ) break; } COMMAND ;
(
DATA
) : ...
endsequence
;
next_statement : ...
The return statement
} aborts
the generation
; of the
current production.
P
Example: O
P
randsequence()
:
TOP : P1
P2 ; P1{ : A B
C;
-
P2 : A- { if( flag == 1 ) return; }BC;
d
A : {e$display( "A" ); } ;
B : { if( flag p== 2 )
t
$display( "B" h); } ;
C : { $display(; "C" ); } ;
endsequence d
o
_
p
o
p
(
)
;
}
e
n
terminals
Repeat that triggered
Production their
Statements generation.
- The repeat
production statement is used to iterate over a
Example:
production a specified number of times.
Example: r
randsequence()
a
...
PUSH_
n
OPER d
: s
repeat
e
( $ura
ndom q
_rang u
e( 2, 6
e
))
PUSH
n
; c
PUSH
e
: ...
endsequen
(
ce
m
a
i
n
Program Block
Topics Covered: )
Program Blocks
Definition & Declaration
Accessing Program Blocks
Relation between Program Block & Module
Advantages of Program Blocks
m
Program Block a
• A program block is a recognition of the differences in goals of writing a design
and testbench. i
• A program block is defined within the n
“program - endprogram” keyword pair.
:
• It serves purposes :
• Provides an entry point to the execution of the testbenches.
• Acts as a scope for data defined within thisfprogram block.
A program block is enclosed within the keywords i
“program – endprogram”.
Example : r
program my_prog ( input clk , input reset , input s
[7:0] in_pkt , output [7:0] out_pkt ); initial
begin t
out_pkt = 8'h0;
end s
endprogram
e
A program module can be defined independently similar to a
module and then instantiated within a module. c
Definition.
n
Example : d
g
n
module my_mod(...); k
program my_prog(...);
endprogram
;
endmodule
In such a case a module variable will also be accessible from the program.
f
Program Block &Module- Similarities & Differences:
Similarities
i
A program block may have zero or more inputs, outputs and inout ports.
r
A program block can contain zero or more initial blocks, assignment
statements and timeunit declarations etc.
s
Type and data declarations, functions and tasks can be defined within a
program
block similar to what you can do in a module.
t
A hierarchy can contain any number of Program blocks. These program
blocks may interact through their ports or may be isolated from one
other.
Differences
A program block is always a leaf level node : in a hierarchy.
A program block can not contain any always statement, UDP,
module, interface or other program.
a
A program definition can occur within a module but a module cannot be
defined this way.
d
• A program can call a task or function in modules or other Programs.
But a module cannot call a task or function in a Program.
•
defined within a module or another Program.
Program Control Tasks e
c
• In addition to the normal simulation control tasks
($stop and $finish), a program can use the $exit
control task.
o
n
d
p
o
p
p
u
s
h
a
d
d
:
g
24
9 e
n
Verilog includes most of( the
statement types of C, except
" for
a
do...while, break, continue
d
and goto
d
Verilog has the repeat and
"
disable statement which ) C
does not.
;
The use of the Verilog
d disable to
e
carry out the functionality of
c
break and continue requires the
user to invent block names.
:
;
p
Procedural assignment evaluation can be
o
modeled as: p
Blocking
:
Non-blocking
g
Procedural assignment execution can be
e
modeled as:
n
Sequential (
"
Concurrent
p
Procedural assignment timing
o controls can
be modeled as: p
"
Delayed evaluations
)
Delayed assignments
;
p
u
s
h
g
e
n
(
"
The = token represents a blocking procedural
p
assignment u
s
Evaluated and assigned in a single step
s
t
r
i
n
g
"
d
o
n
Q1 — (in any order) : e
"
Evaluate RHS of all non-blocking assignments
Evaluate RHS and change LHS of all blocking assignments
Evaluate RHS and change LHS of all continuous assignments
)
Evaluate inputs and change outputs of all primitives
Evaluate and print output from $display and $write
Q2 — (in any order) : :
Change LHS of all non-blocking assignments
Q3 — (in any order) : {
Evaluate and print output
from $monitor and $strobe $d
Call PLI with
reason_synchronize i
Q4 : Call PLI with reason_rosynchronize s
Time 1: p
A <= 1; l
#5 B <= A + 1; a
5) y
end (
always @(posedge clk)
#5 A <= A + 1; s
always @(posedge clk) #5 B <= A + 1;
understand }
= instructor_input;
else if (afternoon)
u
;
nderstand en
s
e
always @(instructor_input)
q
if (morning) u
understand e
= instructor_input;
n
else if (afternoon)
u c
nderstand e
= #5
instructor_i
nput;
else if
(lunch_time)
u
nderstand
= wait (!
lunch_time)
instructor_i
nput;
The value of IN is evaluated when it changes, but is not assigned to OUT until
after 8 clock cycles
Loops:
System Verilog supports the following loop structures :
for
foreach (is used for iteration over the elements of an array)
while
repeat
forever
do.. While (new in SV)
for
(var_dec_or_
assn; expr;
var_assn)
begin
statemen
t_or_null
end
E
x
a
m
p
le
:
m
o
d
u
l
e
f
o
r
_
l
o
o
p
(
)
;
i
n
i
foreach (expr)
t
begin i
statement_or_null
a end
l
Example b :
e
module
g
foreach_loop ();
i byte a [10] =
n
{0,6,7,4,5,66,77,9
9,22,11};
fo
initial
begin r
fore
ach(
(a[i])
i beg
in n
t$display ( "Value of a is ‰g" ,i);
end
#1i $finish ;
end
=
endmodule
Example :
0
module for_each ; initial begin
int src[2][3] ; ;
$display("Initial value:"); foreach (src[i,j])
i
$display("src[%0d][%0d] = %0d", i, j, src[i][j]);
$display("New value:"); src = '{'{9, 8, 7},'{5,3,1}};
<
foreach (src[i,j])
$display("src[%0d][%0d] = %0d", i,4 j, src[i][j]); end
;
endmodule
+
+
)
b
while (expr) e do
begin g begin
statement_or_null i s
n
end t
#1 $display ( "First ->
a of i = ‰g" , i);
Current value
Example : end t
m f
e
o o
m
d r
u ( e
l i n
e n
t
t
w i _
h = o
i r
l 4
_
e ;
_ i n
l > u
o l
o 0
l
p ;
i
( -
) - e
; ) n
b
d
e
b g w
y i h
t n i
e # l
1 e
a
$ (
= d e
x
0 s p
; p r
i l )
n a ;
i y
t E
Jump Statements:
i
System Verilog
(
xa
has following jump statements :
a
l
break àOut of Loop " mp
b S
continue àSkip To End Of The Loop le :
e e
return m
g Expression àExit Fromca Function
o
i àExit From a Task Or o
return Void Function
d
n n
u
w d
Jump Statement- Break l
h
e
Example: i -
l >
module break_loop (); d
initial begin e o
for ( int i = 0 ; i < 10; i ++ ) C
_
begin ( u
#1 $display ( "Current value of i = ‰g" l
a r , i);
if ( i == 5) begin o
$display ( "Coming out of for loop" ); r break ;
o
end end < e
#1 $finish ; p
n
end endmodule1
t
Jump Ststement- Continue (
0
Example )
) v
module continue_loop (); ;
a
initial begin
for ( int i = 0 ; i < 10; i ++ ) l
begin b u
if ((i >= 5) &&e (i < 8)) begin b
e
$display ( " Continue with next iteration" ); continue ; y
end g
t
i
#1 $display ( "Current o , i);
value of i = ‰g"
end e
n f
#1 $finish ;
end endmodule $
a
Jump Statementd– Return i
Example i
=
s =
module return_function (); initial
p
0
l ‰
;
a g
y "
i
( ,
n
i
" i
t
C )
i
begin u ;
printI(); a
r e
#1 $finish ; endr l
n
function void printI; b
e d
begin e
for ( int i = 0 ; in< 10; i ++ ) begin
for ( int
g
if (i >= 5) begint j = 4,k=0 ;
i
return ; j > 0; j
n
end v --,k++ )
$display ( "Current value of i = ‰g" , i); d
a begin
end end o
l #1
endfunction endmodule
u $display
Jump Ststement- Return Expression:
Example : e ("
b
Current
module return_function (); e
initial o value of
g
begin f k=
$display ( "Value returned i
‰g" , from
k); function = ‰g" , printI());
#1 $finish ; n
end a end
$
function int printI; #1
begin = d
$finish;
for ( int i = 0 ; i < 10; i ++ ) i
begin e
s
‰(i n
if >= 5)
begin p
g d
return i; // Value Returned end l
"
$display ( "Current value of i = ‰g" , i);
end a
end y
,
endfunction e
endmodule n
(
Final Block: a d
The final block is like an) initial block, defining a procedural block of
m
statements. "
It occurs at the end of; simulation
o time
C
Executes without adelays. d
u simulation
A final block is typically used to display statistical information about the
u
Example : r
program final_block; + l
r
+ e
initial e
begin n
#40; ;
t
e
n
v
d
a
u
#
e
1
o
$
$finish; f
end f
i
final begin a
n
$display("End of the simulation time =%g\n",$time); end
i
endprogram =
s
h Processes
‰
Topics Covered: g
;
Introduction "
Combinational Logic procedural block - always_comb
Latched Logic procedural block - always_latch
,
e
Sequential Logic procedural block - always_ffa);
n
Continuous assignments a
d
Fork–join Introduction
e
Fork–Join n, Fork–join_any , Fork–Join_none with examples +
+
d
Dynamic Process Control consrutucts - Wait Fork and Disable Fork
m
Introduction: ;
o
Verilog -2001 e
d has always and initial which are static processes
SystemVerilog n
u has both static processes, introduced by always, initial or fork, and
d
dynamic processes, introduced by built-in fork...join_any and fork...join_none.
l
S V creates a thread of execution for each initial or always , for w each parallel
e
statement in a fork –join and for each dynamic processhand each continuous
assignment
repeat (expr) end i
l
begin
e
statement_or_null
end (
a
Example :
<
module repeat_loop ();
always@(a,b);b
// sensitivity list
1
y
beginIntroduction-
t Verilog always block 0
)
e
Sum= a+b; ;
a
Diff = a-b; Prod =a*b;
#
end
0
$
;
f
i
Verilog has general purpose always procedural block
n
Always can represent
i any type of logic (comb/latch/seq) ,used to model algorithmic
i
logic behavior at higher level of abstraction , used in TB to model clock oscillators ,
n
perform tasks repetitive in nature in verification process s
i design intent from the procedural block’s contents – effort on the
Tools must infer
h
part of synthesis/formal
t tools to deduce the type of hardware
Synthesis guidelines for always procedural blocks to be followed (IEEE 1364.1)
i modeling guidelines cannot be enforced for a general
But unfortunately ;
purpose alwaysa procedural block
e
Comes the needl for Specialized procedural blocks which are synthesizable and
unambiguous for the Simulation/Synthesis/Formal/LintingnEDA tools
d
Introduction-SV
b Specialized always block
Comes the neede for Specialized procedural blocks which are synthesizable and
e
unambiguous gfor the Simulation/Synthesis/Formal/Linting EDA tools
System improved upon general purpose always proceduralnblocks of Verilog and
i
introduced specialized always procedural blocks d
Advantages : n m
Are synthesizable r
Documents the design intent and are better modeling choice o than general purpose
e
always procedural block d
p and other EDA tools to check for the synthesis compiler
Require the simulators u
rules e
Unambiguous for the Simulation/Synthesis/Formal/Lintingl EDA tools
a
Helps eliminate potential e
modeling errors early in design process before the models
t
are ready for synthesis
always_comb
SystemVerilog provides a special always_comb procedure for modeling
(
combinational logic behavior.
There is an inferred1 sensitivity list that includes the expressions defined.
0 on the left-hand side of assignments shall not be written to by
The variables written
any other process.
)
Non-ambiguous design intent
The procedure is automatically triggered once at time zero, after all initial and always
blocks have been started so that the outputs of the procedure are consistent with the
inputs
b
Example:
e
always_comb g
a=b&c; i
n
$
d
i
s
p
l
y
always_comb v
o
Differnce between always_comb and always@(*)
f
always_comb get executed once at time 0, always @* waits till a change
occurs on a signal in the inferred sensitivity list
a
Statement within always_comb can't have blocking timing, event control, or fork-
join statement. No such restriction of always @*
EDA tool perform additional
= checks to warn if the behavior
within always_comb procedure doesn't represent combinatorial
logic
‰ side of assignments within an always_comb
Variables on the left-hand
procedure, including variables
g
from the contents of a called function, shall not be written to by any other processes,
"
whereas always @* permits multiple processes to write to the same variable.
always_comb is sensitive to changes within content of a function, whereas always @* is only
sensitive to changes to the, arguments to the function
my_xor(a); always
a
@*
)
my_or(a); endmodule
;
a
+
+
;
e
n
d
#
1
$
always_combf// infers @state –block automatically
//executes i once at time zero , even if not triggered
n
case (state)
i
WAIT : nextstate
s = LOAD ; LOAD : nextstate = STORE
h
; STORE : nextstate = WAIT ;
endcase ;
always_latch:
e
System Verilog also provides a special always_latch procedure for modeling
n behavior.
latched logic
d
The always_latch procedure determines its sensitivity and executes identically to the
e
always_comb procedure.
n
Example :
d
always_latch
m
begin
if ( ck o)
q <= d; d
end u
l
Example:
e
module always_latch_process();
reg [7:0] sum,a,b;
reg parity;
reg enable = 0;
initial begin
$monitor ("@%g a = %h b = %h sum = %h parity = %b", $time, a, b, sum, parity); #2
a = 1; #2 b = 1; #2 a = 10; #2
$finish;
end
always #1 enable = ~enable;
always_latch
begin : ADDER
if (enable) begin
sum <= b + a;
parity <= ^(b + a);
end
end
endmodule
always_ff:
The System Verilog always_ff procedure can be used to model synthesizable
sequential logic behavior.
Variables on the left-hand side of assignments within an always_ff procedure,
including variables from the contents of a called function, shall not be written to by any
other process.
Example :
always_ff@ ( posedge clock iff ( reset == 0) or posedge reset) begin
r1<= reset ? 0 : r2 + 1;
end
always #1 clk ++;
begin : ADDER
if (rst) begin
sum <= 0;
parity <= 0;
^(b + a);
end
end
Continuous Assignments:
In Verilog only net data types can be driven by continuous assignment,
In systemVerilog this restriction is removed and any data type can be driven
using continuous assignment.
Nets can be driven by multiple continuous assignments or by a mixture of primitives and
continuous assignments.
Variables can only be driven by one continuous assignment or one primitive
output
Example : assign binary_out = ( ! enable) ? 0 : (encoder_in ==
16'bxxxx_xxxx_xxxx_xxx1) ;
Fork-Join Introduction:
Fork-join statement has come from Verilog
It is used for forking out parallel processes in test bench.
SV substantially improved fork/join construct to have much more controllability in
process creation, destruction, and waiting for end of the process
There are 3 different kind of join keyword in SV, each specifying a different way of
waiting for completion of the threads/process created by the fork.
join : waits for completion of all of the threads
join_any : waits for the completion of the 1st thread, then comes out
of fork loop, but lets the other process/thread execute as usual
join_none : doesn't wait for completion of any thread, just starts then and immediately
exits fork loop.
value;
input [7:0] delay; begin
#(delay) $display("@%g
Passed Value %d Delay %d“,
$time, value, delay);
end endtask
begin
end join
end
endmodule
Simulator output
example @10:
#10 @40 :
sequential after
#30 @50 :
sequential after
:after join
@140 : final after
#80
endmodule
end Join_any
end
endmodule
@0 : start fork … join –any example
parallel start
@10 :after join_any @20 : parallel
after #10
@40 : sequential after #30 @50 :
sequential after #10
@60 : parallel after #50
@90 : final after #80
begin
end Join_none
$display("@%0dCame out
of fork-join",
$time);
end endmodule
Simulator output
@0 : start fork … join
_none example
@10 :
sequential after
join_none
@10 : parallel
start
@20 : parallel
sequential after
#30
@50 :
sequential after
parallel after
Wait Fork:
Now, suppose you have exited the fork loop by join_none or
join_any and after some steps, you want to wait till completion of
all the threads spanned by the previous fork loop. SV uses "wait
fork" for the same
The wait fork statement is used to ensure that all child processes (processes
created by the calling process) have completed their execution
Example :
fork a;b;c;
join_any
wait fork; // wait for children to complete
#10;
$exit
initial begin
fork
#10 print_value
(10,7);
#20 print_value
(8,5);
#30 print_value
(4,2);
join_none
#50;
endmodule
endmodule
wait fork; endtask
initial begin
fork
join_any
#5 disable fork;
...
end
you have exited fork loop by join_none or join_any and after
some steps, you want to kill just one thread (out of many). The
solution, have named begin end block and call "disable ". (For
example, in the last example if you want to kill only the 2nd
thread after exiting the loop via join_any/join_none, then add
"disable Second_thread;" at the point where you want to disable
the second thread.
fork
begin : First_thread
Co
de
fo
1s
th
re
ad
en
d
eg
in
:
Se
co
nd
_t
hr
ea
d
/
/
C
o
d
e
t
Mailbox:
Mailbox resembles a FIFO in its operation
hMailbox can have a maximum (bounded) size or can be
unlimited (unbounded)
r
Mailbox has in-built methods for access to its contents
eMailbox has to be instantiated before using
Mailbox can take a mix of different data types as its messages or items
a
d
e
nd
join
_any //
can be
Mailbox Declaration:
join_no
; // Inside a program
program mbx_test;
mailbox mbx;
endprogram
// Inside a class
class mbx_class;
mailbox mbx;
endclass
new( ) : Mailbox
constructor which can optionally specify
maximum size. Default size is unlimited.
put (): Puts an item into the Mailbox, blocks if
Mailbox is full
class gen;
r
a
n
d
b
y
t
e
p
a
c
k
e
t
;
e
n
d
c
l
a
s
s
g
e
getn() : The get() method retrieves a
message from a mailbox. Process
g blocks if the Mailbox is empty.
f e
o n
r _
k h
1
;
b
e
g i
i n
n i
tr
i e
a p
l e
ba
e t
g
i (
n
4
g
)
e
n
b
_
e
h
g
i
n
=
e
N
s a
u
t
u m
c b
(
c e
4
e) r
s
o
s b
f
; e
g
i
i
t
n
i e
#
n m
i s
1
t 5
i
i ;
n
a m
l b
m
x
b i
g
e l
e
g b
t
i o
try_get( ) : Tries to retrieve an element from the
(
x
n g
Mailbox. If element is successfully retrieved and
-e
type match " is found, it returns ‘1’. Returns ‘-1’ if
- n
there is a ,type mismatch. Returns ‘0’ if Mailbox is
- _
empty.
- h
m
- 2
bi
- .
x
n p
.
m
ba
t xc n
u
k
m
=e
s )
t
u ;
n)
c m
e;
c b
w
e x
(/
s .
2/
s p
)
; u
;G
et
rep
t(
i ge
n a
te
i
hn t
t
e_
i
h(
a
i11
l
t. 0
b
ep )
c $
g b
d k
i e
i e
n
t g
s
try_peek( ) : Tries
m to copy an element from the
i
b If
Mailbox. p)element is successfully copied and
x l ; isnfound, it returns ‘1’. Returns ‘-1’ if
type match
e aatype mismatch. Returns ‘0’ if Mailbox is
there is
empty =
n . y
i dn ( g
n e $ e
w t n
t
e( i _
2
n m
s ) h
d; e 1
u ,
. f
c P
u o
r
c
t r"
a
es k
G n
s
o d
sf
i bt o
;
v e m
e gt i
ih
z
it ne
e
ni repeat (2)
m (
---statements for
i
i
e t ) into mailbox end
putting
ts ;
e begin
i repeat (10) begin
m
aa success =
n mbx.try_get(gen_h
l f #1.packet);
d
r 3 $display($time, "
bw o ;####Got the
ea suc
i mailbox####
g c
t m ",gen_h1.packet,
i esuccess );
s a
e s
n i
peek( ) : Attempts to copy an element from the Mailbox. If
n s
a m lis successfully copied and type match is found, it
element
d
s b ‘1’.
returns b If there is a type mismatch then run-time error
=
is generated. Process blocks if Mailbox is empty.
x o
m x
bit
j m
a
o
i = s b
i "
l u x
n ,
b .
c
en g
o
n ec
t
x e
d r
w
e n
_ y
i (
s
s 2 h _
s; 2 p
)
f . u
u ;
i p t
l a
n (
l c
it g
.
f e k
ia e
Wo n
l _ t
a r
i kb h
) 1
t e
b
s ; .
eg
ep
g
f ni a
i
o dn c
n
r e k (2) ---statements for
mbx repeat
n = e
m putting into mailbox end
d t
a new(
begin
)
;
l
success =
j b fork $di
mbx.try_peek(gen_h1.pa
o o begin s
cket);
i x p
repeat (2) ---statements for putting
$display($time, " ####Copied the number from
Mailbox Typeless:l
n mailbox####
into mailbox end
",gen_h1.packet, success);
i a
Typeless Mailbox : Mailbox can hold messages of different types.
t e begin
bit bit_type; y
int int_type;
e n
initial begin ( repeat (10) begin
md
mbx = new(2);
mbx.peek(gen_h2.packet);
mbx.put (bit_type); $
s
mbx.put(int_type); t $display($time, " ####Copied the number from
end
mailbox####
Parameterized i : Holds
Mailboxes ",gen_h2.packet, success);
messages of that and equivalent
types. t
: e m
Example o e
e = new ( );
Mailbox #(string) smbox
Mailbox.putn(string_var);
n
Mailbox #(pkt) pmbox, = new ( );
b
Mailbox.putd(pkt);
d
e
Semaphore: "
Semaphores are a key-based synchronization mechanism.
t
Semaphores are typically used for mutual exclusion, access
j
control toashared # and for basic synchronization.
e resources
Semaphore is a Built-In Class and has to be constructed by
k o #
new() before usen
Semaphore e Declaration:
i #
Example n d
n #
program semaphore_test; semaphore sem_key;
endprogram p
e
o methods:
Semaphore u
n
u
d j t
t
. o
t
i
h
n e
n
u
m
b
e
r
t
o
new( ) : Creates
m a Semaphore with a specified
number
a of keys. Default number of keys
is zero.
i
semaphorel key;
b
i o
n x
#
i #
t #
#
i
a "
,
l g
e
n
_
b h
e 1
.
g p
i a
c
n
k
k e
e t
,
y suc
c
e
s
s
n
e : Returns
put()
)
one or more keys into the bucket.
;
w You can put more keys than taken or even not
end
( taken any key.
end
i
2
n
)
i
;
t
i
/
a
/l
n
eb
we
(g
i
)
n
#
o 1
r 6
;
n k
e e
w y
( .
0 g
)
t
(
. 2
key.get(2);
. ) #5;
print(5,8);
. ;
key.put(1); end
z
begin.
e .
#10;
r .
key.get(2);
o .
print(5,9);
.
key.put(2);
end .
k
begin #15;
.
e
key.get(1);
.
y .
print(5,1);
end s .
. .
. .
. .
. .
.
.
k
.
e
.
y
.
.
. p
. u
. t
. (
.
. )
;
get( e) : Obtains one or more keys from the bucket
Examples : blocks if
n if available, requested number of
d keys are not available.
module events_blocking; event e1, e2;
initial
t begin
initial
begin r
$display("@%0d: 2: before trigger",
y
$time);
#16;
-> e2;
_
key.get(2);
@e1;g
$display("@%0d:
e 2: after trigger",
..............
t $time);
end
(
initial begin
)
$display("@%0d: 1: before trigger",
$time);
:
-> e1;
@e2;
T
$display("@%0d: 1: after trigger",
r
$time); end
i
endmodule
e
s
A process can wait for triggered property of the
event.
t
Examples :
o
o
b
t
a
i
n
o
n
e
Examples :
o
r
module event_nb(); event e1,e2; always
wait_order(e1.triggered,e2)
m
o
$info(“events in order”);
r
end emodule
Example
K :
e
y
s
w
Example :
i
fork
t
begin
h
repeat (5) begin
o
gen_h1.randomiz
u
e(); #3;
t mbx.put(gen_h1.
packet);
b $display($time," Put the number into mailbox
l ",gen_h1.packet ); @wait_for_receive_permit;
oend
c
end
k
i
begin
n
repeat (5) begin
g
mbx.get(gen_h2.pack
.
T
a
k
e
Example :
s
t
h
e
k
e
y
s
i
f
a
v
a
i
l
b
a
l
e
a
n
d
r
e
t
u
r
n
s
Example :
fork
‘
begin
1
repeat (5) begin
’
gen_h1.randomiz
e(); #3;
e
mbx.put(gen_h1.
l
packet);
s
$display($time," Put the number into mailbox
e
",gen_h1.packet ); @wait_for_receive_permit;
end
end r
e
t
begin
u
repeat (5) begin
r
mbx.get(gen_h2.pack
n
et); # 14;
s
$display($time," Got
the number from
‘
mailbox ",
0
gen_h2.packet);
-> ’
.
wait_for_receive_permit;
i end
nend
join
i
t
i
a
l
e
g
i
n
. #
1
6
;
k
e Coverage:
Topics Covered:
y
Introduction to Coverage
. Coverage Model - Cover group
Defining
Using Cover Groups in Classes
t Coverage Points
Defining
Defining Cross Coverage
r
Specifying Coverage Options
Pre - Defined Coverage Methods
y
PreDefined Coverage Syst em Tasks & Functions
_
g
e
t
(
3
)
;
option
option_name =
expression;
endgroup
covergro
up_name
covergroup_inst
= new;
The “covergroup” construct is a user-defined
type. The type definition is written once, and
multiple instances of that type can be created in
different contexts. Similar to a class, once
defined, a covergroup instance can be created via
the new() operator.
A covergroup can be defined in a “module”,
“program”, “interface” or
“class”.
ACCUM_COVERAGE
DATA_COVERAGE
endgroup
coveralu c1 = new;
You do this by creating coverpoint
bins:
bins a[]= {0,2, [8:20]};
34
0
Cover_cross ::= cross list_of_coverpoints
}}
; Bins_or_options ::=
cross_auto_bin_max
Agenda
Introduction
Types of assertions
◦ Immediate
◦ Concurrent
Writing properties Sequences
Sequence composition
◦ And, or, intersect
Advanced SVA features
◦ Expect, binding
Assertion Coverage
An assertion specifies a behavior of the system.
Assertions are primarily used to validate the behavior of a design. ("Is it working
correctly?“)
Assertions can be used to capture the information about various level of properties.
assertions can be used to provide functional coverage and generate input stimulus for
validation.
assert(condition)
else begin
…….
…….
$fatal(“Condition is False”);
end
The immediate assertion statement is a test of an expression performed when the statement is
executed in the procedural code.
Works like an if statement.
if the expression evaluates to X, Z or 0, then it is interpreted as being false and the assertion
is said to fail.
Ex: assertion_label : assert (expression) pass block code;
else fail block code;
Where:
assertion_label : User defined assertion label.
assert : SystemVerilog reserve word, used for assertion.
expression : Any valid verilog expression.
pass block code : Code that gets executed when assertion passes.
else : Optional syntax to specify failed code.
fail block code : Code that gets executed when assertion fails.
Severity levels- Limitations of Immediate Assertions :
SystemVerilog provides various assertions levels for reporting messages as listed below.
$fatal is a run-time fatal.
$error is a run-time error.
$warning is a run-time warning, which can be suppressed in a tool-specific manner.
$info indicates that the assertion failure carries no specific severity.
One can use $display, or any regular verilog code, like triggering a event, or incrementing a
counter, or calling function in pass/fail block code.
Instantaneous boolean check only.
xtra code required around assertion for complex or temporal checks.
Complexity becomes comparable to that of design.
Requires as much debugging as design.
Poor readability as design intent is not obvious.
Example:
Ex: always@(posedge clk) case(state)
`state1: …
`state2:
…
default:
adef: assert(1’b0)
$warning(“case default”);
endcase
Assertion just used to report problem.
Concurrent Assertions: Concurrent assertions check the sequence of events spread over
multiple clock cycles.
The Keyword differentiates the immediate assertion from the concurrent assertion is "property."
Concurrent assertion is evaluated only at the occurrence of a clock tick.
The keyword property distinguishes a concurrent assertion from an immediate assertion.
The values of variables used in assertions are sampled in the Preponed region of a time slot
and the assertions are evaluated during the Observe region.
Concurrent assertions may also be used as statements in initial or always blocks.
A concurrent assertion in an initial block is only tested on the first clock tick
Design behavior:
◦ Signals Read and Write are never both high at the positive edge of clock.
The functionality is represented by the combination of multiple logical events. These events
could be simple Boolean expressions.
Sequence:
Boolean expression events that evaluate over a period of time involving single/multiple clock
cycles. SVA provides a key word to represent these events called "sequence."
syntax:
sequence name_of_sequence;
……
endsequence
Property:
syntax :
property name_of_property;
test expression or
endproperty
Assert:
The property is the one that is verified during a simulation. It has to be asserted to take effect
during a simulation. SVA provides a key word called "assert" to check the property.
syntax:
Sequence seq_1 checks that the signal "a" is high on every positive edge of the clock. If signal
"a" is not high on any positive clock edge, the assertion will fail.
sequence seq_1;
endsequence
Below sequence seq_2 checks that on every positive edge of the clock, either signal "a" or signal
"b" is high. If both the signals are low, the assertion will fail.
sequence seq_2;
@(posedge clk) a || b;
endsequence
Sequence Expressions:
By defining arguments in a sequence definition, the same sequence can be re-used for the similar
behavior.
sequence s_lib_inst
seq_lib(req1,req2);
endsequence
In SVA, clock cycle delays are represented by a "##" sign. For example, ##2 means 2 clock
cycles.
Below sequence checks for the signal "a" being high on a given positive edge of the clock. If
signal "a" is not high, then the sequence fails. If signal "a" is high on any given positive edge of
clock, then signal "b" should be high 2 clock cycles after that. If signal "b" is not asserted after 2
clock cycles, the assertion fails.
sequence seq;
@(posedge clk) a ##2 b;
Endsequence
Note :: sequence begins when signal "a" is high on a positive edge of the clock.
sequence seq;
a ##2 b;
endsequence
property p;
@(posedge clk) seq;
endproperty
a_1 : assert property(p);
In general, it is a good idea to define the clocks in property definitions and keep the sequences
independent of the clocks. This will help increase the re-use of the basic sequence definitions.
A separate property definition is not needed to assert a sequence. the expression to be checked
can be
called from the assert statement directly as shown below.
Forbidding a property
In all the examples shown so far, the property is checking for a true condition. we expect the
property to be false always. If the property is true, the assertion fails.
sequence seq;
a ##2 b;
endsequence
a_2: assert property(@(posedge clk) seq);
Calling a property with a clock definition from within the assert statement is not allowed.
a_3: assert property(@(posedge clk) p) ; //Not allowed
Below sequence checks that if signal "a" is high on a given positive edge of the clock, then after
2 clock cycles, signal "b" shall not be high. The keyword "not" is used to specify that the
property should never be true.
sequence seq;
@(posedge clk) a ##2 b;
endsequence
property p;
not seq;
endproperty
a_1: assert property(p);
SystemVerilog Implication operator
Implication Operator
sequence seq;
endsequence
In the above sequence we can observe that, sequence starts on every positive edge of the clock
and it looks for "a" to be high on every positive clock edge. If signal "a" is not high on any
given positive clock edge, an error is issued by the checker.
If we want sequence to be checked only after “a” is high, this can achieved by using implication
operator.
Implication is equivalent to an if-then structure. The left hand side of the implication is called
the "antecedent" and the right hand side is called the "consequent." The antecedent is the gating
condition. If the antecedent succeeds, then the consequent is evaluated.
The implication construct can be used only with property definitions. It cannot be used in
sequences.
Overlapped implication
Non-overlapped implication
Overlapped implication
If there is a match on the antecedent, then the consequent expression is evaluated in the same
clock cycle.
Below property checks that, if signal "a" is high on a given positive clock edge, then signal "b"
should also be high on the same clock edge.
property p;
endproperty
a: assert property(p);
Non-overlapped implication
If there is a match on the antecedent, then the consequent expression is evaluated in the next
clock cycle.
Below property checks that, if signal "a" is high on a given positive clock edge, then signal "b"
should be high on the next clock edge.
property p;
endproperty
a: assert property(p);
Implication with a fixed delay on the consequent :
Below property checks that, if signal "a" is high on a given positive clock edge, then signal "b"
should be high after 2 clock cycles.
property p;
endproperty
a: assert property(p);
Implication with a sequence as an antecedent :
Below property checks that, if the sequence seq_1 is true on a given positive edge of the clock,
then starts checking the seq_2 (“d” should be low, 2 clock cycles after seq_1 is true ) .
sequence seq_1;
(a && b) ##1 c;
endsequence
sequence seq_2;
##2 !d;
endsequence
property p;
@(posedge clk) seq_1 |-> seq_2;
endpeoperty
a: assert property(p);
Below property checks that, if signal "a" is high on a given positive clock edge, then within 1 to
4 clock cycles, the signal "b" should be high.
property p;
@(posedge clk) a |-> ##[1:4] b;
endproperty
a: assert property(p);
Below property checks that, if signal "a" is high on a given positive clock edge,then signal "b"
should be high in the same clock cycle or within 4 clock cycles.
property p;
@(posedge clk) a |-> ##[0:4] b;
endproperty
a: assert property(p);
Below property checks that, if signal "a" is high on a given positive clock edge, then signal "b"
will be high eventually starting from the next clock cycle.
property p;
@(posedge clk) a |-> ##[1:$] b;
endproperty
a: assert property(p);
SystemVerilog Repetition operators
Repetition Operators
property p;
endproperty
a: assert property(p);
The above property checks that, if the signal “a” is high on given posedge of clock, then signal
“b” should be high for 3 consecutive clock cycles.
The Consecutive repetition operator is used to specify that a signal or a sequence will match
continuously for the number of clocks specified.
Syntax
signal [*n] or sequence [*n]
"n" is the number of repetitions.
with repetition operator above sequence can be re-written as,
property p;
endproperty
a: assert property(p);
go-to repetition
The go-to repetition operator is used to specify that a signal will match the number of times
specified not necessarily on continuous clock cycles.
Signal [->n]
property p;
endproperty
a: assert property(p);
the above property checks that, if the signal “a” is high on given posedge of clock, then signal
“b” should be high for 3 clock cycles followed by “c” should be high after ”b” is high for third
time.
Non-consecutive repetition
This is very similar to "go to" repetition except that it does not require that the last match on the
signal repetition happen in the clock cycle before the end the entire sequence matching.
Signal [=n]
$rose
returns true if the least significant bit of the expression changed to 1. Otherwise, it returns false.
sequence seq_rose;
endsequence
Sequence seq_rose checks that the signal "a" transitions to a value of 1 on every positive edge of
the clock. If the transition does not occur, the assertion will fail.
$fell
$fell( boolean expression or signalname) returns true if the least significant bit of the expression
changed to 0. Otherwise, it returns false.
sequence seq_fell;
endsequence
Sequence seq_fell checks that the signal "a" transitions to a value of 0 on every positive edge of
the clock. If the transition does not occur, the assertion will fail.
$stable
$stable(boolean expression or signalname) returns true if the value of the expression did not
change. Otherwise, it returns false.
sequence seq_stable;
endsequence
Sequence seq_stable checks that the signal "a" is stable on every positive edge of the clock. If the
there is any transition occur, the assertion will fail.
$past
$past(signal_name, number of clock cycles) provides the value of the signal from the previous
clock cycle.
Below Property checks that, in the given positive clock edge, if the “b” is high, then 2 cycles
before that, a was high.
property p;
endproperty
a: assert property(p);
$past construct with clock gating :
The $past construct can be used with a gating signal. on a given clock edge, the gating signal has
to be true even before checking for the consequent condition.
Below Property checks that, in the given positive clock edge, if the “b” is high, then 2 cycles
before that, a was high only if the gating signal "c' is valid on any given positive edge of the
clock.
Property p;
@(posedge clk) b |-> ($past(a,2,c) == 1);
endproperty
a: assert property(p);
Built-in system functions
$onehot(expression)
- checks that only one bit of the expression can be high on any given clock edge.
$onehot0(expression)
- checks only one bit of the expression can be high or none of the bits can be high on any given
clock edge.
$isunknown(expresslon)
- checks if any bit of the expression is X or Z.
$countones(expression)
- counts the number of bits that are high in a vector.
Assert statement a_1 checks that the bit vector "state" is one-hot.
Assert statement a_2 checks that the bit vector "state" is zero one-hot.
Assert statement a_3 checks if any bit of the vector "bus" is X or Z.
Assert statement a_4 checks that the number of ones in the vector "bus" is greater than one.
SystemVerilog disable iff and ended construct
disable iff
In certain design conditions, we don't want to proceed with the check if some condition is true.
this can be achieved by using disable iff.
Below property checks that, if the signal “a” is high on given posedge of clock, then signal “b”
should be high for 3 clock cycles followed by “c” should be high after ”b” is high for third time.
During this entire sequence, if reset is detected high at any point, the checker will stop.
property p;
@(posedge clk)
endproperty
a: assert property(p);
ended
while concatenating the sequences, ending point of the sequence can used as a synchronization
point.
sequence seq_1;
(a && b) ##1 c;
endsequence
sequence seq_2;
d ##[4:6] e;
endsequence
property p;
endpeoperty
a: assert property(p);
Above property checks that, sequence seq_1 and SEQ_2 match with a delay of 2 clock cycle in
between them. the end point of the sequences does the synchronization.
Simple Assertions
SVA Layers
Steps Involved in writing Assertions:
20
5
Assert Property
System Verlog Scheduling Semantics:
This section gives an overview of the interactions and behavior of SystemVerilog elements,
especially with respect to the scheduling and execution of events.
Updates to IEEE STD 1800-20051 divide the SystemVerilog time slot into 17 ordered
regions, nine ordered regions for execution of SystemVerilog statements and eight ordered
regions for execution of PLI code. The purpose of dividing a time slot into these ordered
regions is to provide predictable interactions between the design and testbench code.
Every change in state of a net or variable in the system description being simulated is
considered an update event. When an update event is executed, all the processes that are
sensitive to those events are considered for evaluation known as an evaluation event.
Example of processes include, initial, always, always_comb, always_latch, and always_ff
procedural blocks, continuous assignments, asynchronous tasks, and procedural
assignment statements.
20
1
A single time slot is divided into multiple regions where events can be scheduled. This event
scheduling supports in obtaining a clear and predictable interactions that provide for an
ordering of particular types of execution.
This allows properties and checkers to sample data when the design under test is in a
stable state. Property expressions can be safely evaluated, and testbenches can react to
both properties and checkers with zero delay, all in a predictable manner.
This same mechanism also allows for non-zero delays in the design, clock propagation,
and/or stimulus and response code to be mixed freely and consistently with cycle accurate
descriptions.
Note:
1. The term simulation time is used to refer to the time value maintained by the simulator to
model the actual time it would take for the system description being simulated.
2. A time slot include all simulation activity that is processed in the event regions for each
simulation time SystemVerilog event Regions The new SystemVerilog event regions are
developed to support new SystemVerilog constructs and also to prevent race conditions
being created between the RTL design and the new verification constructs.
These new regions guarantee predictability and consistency between design, testbenches,
and assertions
Preponed region
The values of variables that are used in concurrent assertions are sampled in Preponed
region. (Evaluation is done at observed region). Preponed region is executed only once in
each time slot, immediately after advancing simulation time.
Pre-active region
The Pre-active region is specifically for a PLI callback control point that allows for user code
to read and write values and create events before events in the Active region are evaluated
Active region
The Active region holds current events being evaluated and can be processed in any order.
Inactive region
The Inactive region holds the events to be evaluated after all the active events are
processed. In this region #0 blocking assignments are scheduled.
Pre-NBA region
The Pre-NBA region is specifically for a PLI callback control point that allows for user code
to read and write values and create events before the events in the NBA region are
evaluated
The principal function of this region is to execute the updates to the Left-Hand-Side (LHS)
variables that were scheduled in the Active region for all currently executing nonblocking
assignments.
Post-NBA region
The Post-NBA region is specifically for a PLI callback control point that allows for user code
to read and write values and create events after the events in the NBA region are evaluated
Observed region
The principal function of this region is to evaluate the concurrent assertions using the values
sampled in the Preponed region. A criterion behind this decision is that the property
evaluations must only occur once in any clock triggering time slot. During the property
evaluation, pass/fail code shall be scheduled in the Reactive region of the current time slot.
Post-observed region
The Post-observed region is specifically for a PLI callback control point that allows for user
code to read values after properties are evaluated (in Observed or earlier region).
Reactive region
Code specified in the program block, and pass/fail code from property expressions, are
scheduled in the Reactive region. The principal function of this region is to evaluate and
execute all current program activity in any order
Execute all program blocking assignments.
Execute the pass/fail code from concurrent assertions.
Evaluate the Right-Hand-Side (RHS) of all program nonblocking assignments and
schedule
Execute all program continuous assignments
Execute the $exit and implicit $exit commands
Postponed Region
The principal function of this region is to execute the $strobe and $monitor commands that
will show the final updated values for the current time slot. This region is also used to collect
functional coverage for items that use strobe sampling.
System Verilog Environment/Test Bench
Verification Environment or Testbench is used to check the functional correctness of
the Design Under Test (DUT) by generating and driving a predefined input sequence to a
design, capturing the design output and comparing with-respect-to expected output.
Defines the pin level activity generated by agent (to drive to DUT through
the driver) or the activity has to be observed by agent (Placeholder for
transaction class
the activity monitored by monitor on DUT signals).
Generates the stimulus (create and randomize the transaction class) and
generator class
send it to Driver.
Receives the stimulus (transaction) from generator and drives the packet
driver class
level data inside transaction into pin level (to DUT).
Observes pin level activity on interface signals and converts into packet
monitor class
level which is sent to the components such as scoreboard.
Agent is container class, which groups the class’s (generator, driver and
agent class
monitor) specific to an interface or protocol.
Receives data items from monitors and compares with expected values.
scoreboard class Expected values can be either golden reference values or generated from
reference model.
Lets Write the SystemVerilog TestBench for the simple design "ADDER".
Before writing the SystemVerilog TestBench, will look into the design specification.
ADDER:
Below is the block diagram of ADDER.
Valid signal indicates the valid value on the a and b, On valid signal adder will add the a and b,
drives the result in next clock on c.
Adder add/Sum the 4bit values 'a' and 'b', and drives the result on c in the next clock.
waveform diagram:
"Adder" Waveform
Transaction Class
class transaction;
bit [6:0] c;
$display("-------------------------");
$display("- %s ",name);
$display("-------------------------");
$display("- c = %0d",c);
$display("-------------------------");
endfunction
endclass
INPUT GENERATOR:
class generator;
int repeat_count;
mailbox gen2driv;
event ended;
//constructor
//getting the mailbox handle from env, in order to share the transaction packet between the
generator and driver, the same mailbox is shared between both.
this.gen2driv = gen2driv;
endfunction
//main task, generates(create and randomizes) the repeat_count number of transaction packets
and puts into mailbox
task main();
repeat(repeat_count) begin
trans = new();
gen2driv.put(trans);
end
endtask
endclass
DRIVER :
class driver;
int no_transactions;
//constructor
this.vif = vif;
this.gen2driv = gen2driv;
endfunction
task reset;
wait(vif.reset);
vif.a <= 0;
vif.b <= 0;
vif.valid <= 0;
wait(!vif.reset);
endtask
task main;
forever begin
transaction trans;
gen2driv.get(trans);
@(posedge vif.clk);
vif.valid <= 1;
vif.a <= trans.a;
@(posedge vif.clk);
vif.valid <= 0;
trans.c = vif.c;
@(posedge vif.clk);
no_transactions++;
end
endtask
endclass
MONITOR:
class monitor;
mailbox mon2scb;
//constructor
this.vif = vif;
this.mon2scb = mon2scb;
endfunction
//Samples the interface signal and send the sample packet to scoreboard
task main;
forever begin
transaction trans;
trans = new();
@(posedge vif.clk);
wait(vif.valid);
trans.a = vif.a;
trans.b = vif.b;
@(posedge vif.clk);
trans.c = vif.c;
@(posedge vif.clk);
mon2scb.put(trans);
end
endtask
endclass
SCOREBOARD:
class scoreboard;
mailbox mon2scb;
int no_transactions;
//constructor
this.mon2scb = mon2scb;
endfunction
task main;
transaction trans;
forever begin
mon2scb.get(trans);
if((trans.a+trans.b) == trans.c)
$display("Result is as Expected");
else
no_transactions++;
end
endtask
endclass
ENVIRONMENT:
`include "transaction.sv"
`include "generator.sv"
`include "driver.sv"
`include "monitor.sv"
`include "scoreboard.sv"
class environment;
generator gen;
driver driv;
monitor mon;
scoreboard scb;
//mailbox handle's
mailbox gen2driv;
mailbox mon2scb;
//virtual interface
//constructor
this.vif = vif;
//creating the mailbox (Same handle will be shared across generator and driver)
gen2driv = new();
mon2scb = new();
gen = new(gen2driv);
driv = new(vif,gen2driv);
mon = new(vif,mon2scb);
scb = new(mon2scb);
endfunction
//
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
mon.main();
scb.main();
join_any
endtask
task post_test();
wait(gen.ended.triggered);
wait(gen.repeat_count == scb.no_transactions);
endtask
//run task
task run;
pre_test();
test();
post_test();
$finish;
endtask
endclass
PROGRAM BLOCK(RANDOM_TEST):
`include "environment.sv"
environment env;
initial begin
//creating environment
env = new(i_intf);
env.gen.repeat_count = 4;
//calling run of env, it interns calls generator and driver main tasks.
env.run();
end
endprogram
PROGRAM BLOCK(DIRECT_TEST):
`include "environment.sv"
a.rand_mode(0);
b.rand_mode(0);
a = 10;
b = 12;
endfunction
endclass
environment env;
my_trans my_tr;
initial begin
//creating environment
env = new(i_intf);
my_tr = new();
env.gen.repeat_count = 10;
env.gen.trans = my_tr;
//calling run of env, it interns calls generator and driver main tasks.
env.run();
end
endprogram
INTERFACE:
interface intf(input logic clk,reset);
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [6:0] c;
endinterface
TESTBENCH:
//including interfcae and testcase files
`include "interface.sv"
//-------------------------[NOTE]---------------------------------
`include "random_test.sv"
//`include "directed_test.sv"
//----------------------------------------------------------------
module tbench_top;
bit clk;
bit reset;
//clock generation
//reset Generation
initial begin
reset = 1;
#5 reset =0;
end
intf i_intf(clk,reset);
test t1(i_intf);
//DUT instance, interface signals are connected to the DUT ports
adder DUT (
.clk(i_intf.clk),
.reset(i_intf.reset),
.a(i_intf.a),
.b(i_intf.b),
.valid(i_intf.valid),
.c(i_intf.c)
);
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
endmodule
Testbench Architecture
Writing TestBench
Before writing/creating the verification plan need to know about design, so will go through the
design specification.
Signal Defination:
Write Operation:
address, wr_en and wdata should be driven at the same clock cycle.
Read Operation:
address and rd_en should be driven on the same clock cycle, Design will respond with the data in
the next clock cycle.
Design Features,
The Memory model is capable of storing 8bits of data per address location.
o Perform write to any memory location, read from the same memory location, read
data should be same as written data
o Perform write and read to all the memory locations (as address is 2bit width the
possible address are 2‘b00, 2’b01, 2’b10 and 2’b11)
o Check default memory values. (before writing any locations, do read operation we
should get default values as ‘hFF)
o Assert reset in between write/read operation and check for default values. (after
writing to few locations assert the reset and perform read operation, we should get
default memory location value ‘hFF)
TestBench Architecture:
Transaction Class:
Fields required to generate the stimulus are declared in the transaction class.
Transaction class can also be used as placeholder for the activity monitored by monitor
on DUT signals.
So, first step is to declare the 'Fields' in the transaction class.
Below are the steps to write transaction class.
1. Declaring the fields.
class transaction;
endclass
endclass
3. Either write or read operation will be performed at once, so wr_en or rd_en is generated
by 'adding constraint'.
class transaction;
endclass
Generator Class:
class generator;
------
endclass
endclass
//main task, generates(create and randomizes) the packets and puts into
mailbox
task main();
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
endtask
endclass
This involves,
//declaring mailbox
mailbox gen2driv;
//constructor
function new(mailbox gen2driv);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
endfunction
//main task, generates(create and randomizes) the packets and puts into
mailbox
task main();
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
endtask
endclass
//declaring mailbox
mailbox gen2driv;
//constructor
function new(mailbox gen2driv);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
endfunction
endclass
5. Adding event to indicate the completion of generation process, event will be triggered on the
completion of Generation process.
class generator;
//declaring mailbox
mailbox gen2driv;
//constructor
function new(mailbox gen2driv,event ended);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
this.ended = ended;
endfunction
repeat(repeat_count) begin
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
end
-> ended;
endtask
endclass
Interface:
Interface will group the signals, specifies the direction (Modport) and Synchronize the
signals(Clocking Block).
//monitor modport
modport MONITOR (clocking monitor_cb,input clk,reset);
//driver modport
modport DRIVER (clocking driver_cb,input clk,reset);
//monitor modport
modport MONITOR (clocking monitor_cb,input clk,reset);
endinterface
Driver Class:
Driver class is responsible for,
receive the stimulus generated from generator and drive to DUT by assigning
transaction class values to interface signals.
class driver;
----
endclass
1. Declare interface and mailbox, Get the interface and mailbox handle through constructor.
//creating virtual interface handle
virtual mem_intf mem_vif;
//constructor
function new(virtual mem_intf mem_vif,mailbox gen2driv);
//getting the interface
this.mem_vif = mem_vif;
//getting the mailbox handle from environment
this.gen2driv = gen2driv;
endfunction
2. Adding reset task, which initializes the Interface signals to default values.
4. Adding local variable to track the number of packets driven, and increment the variable in
drive task.
(This will be useful to end the test-case/Simulation. i.e compare the generated pkt's and driven
pkt's, if both are same then end the simulation)
//constructor
function new(virtual mem_intf mem_vif,mailbox gen2driv);
//getting the interface
this.mem_vif = mem_vif;
//getting the mailbox handle from environment
this.gen2driv = gen2driv;
endfunction
endclass
Environment:
Environment is container class contains Mailbox, Generator and Driver.
Creates the mailbox, generator and driver, shares the mailbox handle across the Generator and
Driver.
class environment;
---
endclass
//mailbox handle's
mailbox gen2driv;
//virtual interface
virtual mem_intf mem_vif;
Mailbox
Generator
Driver
and pass the interface handle through new() method.
//constructor
function new(virtual mem_intf mem_vif);
//get the interface from test
this.mem_vif = mem_vif;
//creating the mailbox (Same handle will be shared across generator and
driver)
gen2driv = new();
//creating generator and driver
gen = new(gen2driv,gen_ended);
driv = new(mem_vif,gen2driv);
endfunction
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
join_any
endtask
task post_test();
wait(gen_ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
endtask
//virtual interface
virtual mem_intf mem_vif;
//constructor
function new(virtual mem_intf mem_vif);
//get the interface from test
this.mem_vif = mem_vif;
//creating the mailbox (Same handle will be shared across generator and
driver)
gen2driv = new();
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
join_any
endtask
task post_test();
wait(gen_ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
endtask
//run task
task run;
pre_test();
test();
post_test();
$finish;
endtask
endclass
Test:
Test code is written with the program block.
program test;
----
endprogram
initial begin
//creating environment
env = new(intf);
end
initial begin
//creating environment
env = new(intf);
//calling run of env, it interns calls generator and driver main tasks.
env.run();
end
endprogram
TestBench Top:
This is the top most file, which connects the DUT and TestBench.
TestBench top consists of DUT, Test and Interface instances.
Interface connects the DUT and TestBench.
module tbench_top;
---
endmodule
//clock generation
always #5 clk = ~clk;
//reset Generation
initial begin
reset = 1;
#5 reset =0;
End
module tbench_top;
//clock generation
always #5 clk = ~clk;
//reset Generation
initial begin
reset = 1;
#5 reset =0;
end
Monitor :
Samples the interface signals and convert the signal level activity to transaction level.
Send the sampled transaction to Scoreboard via Mailbox.
Below are the steps to write monitor.
1. Writing monitor class.
class monitor;
------
endclass
2. Declare interface and mailbox, Get the interface and mailbox handle through constructor.
//constructor
function new(virtual intf vif,mailbox mon2scb);
//getting the interface
this.vif = vif;
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
task main;
forever begin
transaction trans;
trans = new();
@(posedge mem_vif.MONITOR.clk);
wait(`MON_IF.rd_en || `MON_IF.wr_en);
trans.addr = `MON_IF.addr;
trans.wr_en = `MON_IF.wr_en;
trans.wdata = `MON_IF.wdata;
if(`MON_IF.rd_en) begin
trans.rd_en = `MON_IF.rd_en;
@(posedge mem_vif.MONITOR.clk);
@(posedge mem_vif.MONITOR.clk);
trans.rdata = `MON_IF.rdata;
end
mon2scb.put(trans);
end
endtask
//constructor
function new(virtual mem_intf mem_vif,mailbox mon2scb);
//getting the interface
this.mem_vif = mem_vif;
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
//Samples the interface signal and send the sample packet to scoreboard
task main;
forever begin
transaction trans;
trans = new();
@(posedge mem_vif.MONITOR.clk);
wait(`MON_IF.rd_en || `MON_IF.wr_en);
trans.addr = `MON_IF.addr;
trans.wr_en = `MON_IF.wr_en;
trans.wdata = `MON_IF.wdata;
if(`MON_IF.rd_en) begin
trans.rd_en = `MON_IF.rd_en;
@(posedge mem_vif.MONITOR.clk);
@(posedge mem_vif.MONITOR.clk);
trans.rdata = `MON_IF.rdata;
end
mon2scb.put(trans);
end
endtask
endclass
Scoreboard :
if the transaction type is "read", compares the read data with the local memory data.
if the transaction type is "write", local memory will be written with the wdata.
class scoreboard;
------
endclass
1. Declaring the mailbox and variable to keep count of transactions, connecting handle through
constructor,
//creating mailbox handle
mailbox mon2scb;
//constructor
function new(mailbox mon2scb);
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
no_transactions++;
end
endtask
class scoreboard;
//constructor
function new(mailbox mon2scb);
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
foreach(mem[i]) mem[i] = 8'hFF;
endfunction
no_transactions++;
end
endtask
endclass
Environment:
Here only updates are mentioned. i.e adding monitor and scoreboard to previous example.
//mailbox handle's
mailbox gen2driv;
mailbox mon2scb; //---NEW CODE---
//virtual interface
virtual mem_intf mem_vif;
Mailbox (mon2scb)
Monitor
Scoreboard
and pass the interface handle through new() method.
//constructor
function new(virtual mem_intf mem_vif);
//get the interface from test
this.mem_vif = mem_vif;
//creating the mailbox (Same handle will be shared across generator and
driver)
gen2driv = new();
mon2scb = new();
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
mon.main(); //---NEW CODE---
scb.main(); //---NEW CODE---
join_any
endtask
task post_test();
wait(gen.ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
wait(gen.repeat_count == scb.no_transactions); //---NEW CODE---
endtask
class environment;
//mailbox handle's
mailbox gen2driv;
mailbox mon2scb;
//virtual interface
virtual mem_intf mem_vif;
//constructor
function new(virtual mem_intf mem_vif);
//get the interface from test
this.mem_vif = mem_vif;
//creating the mailbox (Same handle will be shared across generator and
driver)
gen2driv = new();
mon2scb = new();
//
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
mon.main();
scb.main();
join_any
endtask
task post_test();
wait(gen_ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
wait(gen.repeat_count == scb.no_transactions);
endtask
//run task
task run;
pre_test();
test();
post_test();
$finish;
endtask
endclass
In this example, we verify a simple synchronous FIFO. Of course in real life we really don't get
to verify a FIFO model, as in companies this are generated using script.
This testbench will slightly different from what we have seen till now.
So the verification components are split into following blocks
Push Generator
Pop Generator
Push Monitor
Pop Monitor
Scoreboard
SystemVerilog testbench top
SystemVerilog Interface file
HDL Testbench top
We are going to have some more components that like reset. Push/Pop Driver, Push/Pop Monitor
are going to be part of fifo_driver.sv
//-----------------------------------------------------
// Design Name : syn_fifo
// File Name : syn_fifo.v
// Function : Synchronous (single clock) FIFO
// //-----------------------------------------------------
module syn_fifo (
clk , // Clock input
rst , // Active high reset
wr_cs , // Write chip select
rd_cs , // Read chipe select
data_in , // Data input
rd_en , // Read enable
wr_en , // Write Enable
data_out , // Data Output
empty , // FIFO empty
full // FIFO full
);
// FIFO constants
parameter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 8;
parameter RAM_DEPTH = (1 << ADDR_WIDTH);
// Port Declarations
input clk ;
input rst ;
input wr_cs ;
input rd_cs ;
input rd_en ;
input wr_en ;
input [DATA_WIDTH-1:0] data_in ;
output full ;
output empty ;
output [DATA_WIDTH-1:0] data_out ;
//-----------Internal variables-------------------
reg [ADDR_WIDTH-1:0] wr_pointer;
reg [ADDR_WIDTH-1:0] rd_pointer;
reg [ADDR_WIDTH :0] status_cnt;
reg [DATA_WIDTH-1:0] data_out ;
wire [DATA_WIDTH-1:0] data_ram ;
//-----------Variable assignments---------------
assign full = (status_cnt == (RAM_DEPTH-1));
assign empty = (status_cnt == 0);
//-----------Code Start---------------------------
always @ (posedge clk or posedge rst)
begin : WRITE_POINTER
if (rst) begin
wr_pointer <= 0;
end else if (wr_cs && wr_en ) begin
wr_pointer <= wr_pointer + 1;
end
end
ram_dp_ar_aw #(DATA_WIDTH,ADDR_WIDTH)DP_RAM (
.address_0 (wr_pointer) , // address_0 input
.data_0 (data_in) , // data_0 bi-directional
.cs_0 (wr_cs) , // chip select
.we_0 (wr_en) , // write enable
.oe_0 (1'b0) , // output enable
.address_1 (rd_pointer) , // address_q input
.data_1 (data_ram) , // data_1 bi-directional
.cs_1 (rd_cs) , // chip select
.we_1 (1'b0) , // Read enable
.oe_1 (rd_en) // output enable
);
endmodule
//-----------------------------------------------------
// Design Name : ram_dp_ar_aw
// File Name : ram_dp_ar_aw.v
// Function : Asynchronous read write RAM
////-----------------------------------------------------
module ram_dp_ar_aw (
address_0 , // address_0 Input
data_0 , // data_0 bi-directional
cs_0 , // Chip Select
we_0 , // Write Enable/Read Enable
oe_0 , // Output Enable
address_1 , // address_1 Input
data_1 , // data_1 bi-directional
cs_1 , // Chip Select
we_1 , // Write Enable/Read Enable
oe_1 // Output Enable
);
parameter DATA_WIDTH = 8 ;
parameter ADDR_WIDTH = 8 ;
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
//--------------Input Ports-----------------------
input [ADDR_WIDTH-1:0] address_0 ;
input cs_0 ;
input we_0 ;
input oe_0 ;
input [ADDR_WIDTH-1:0] address_1 ;
input cs_1 ;
input we_1 ;
input oe_1 ;
//--------------Inout Ports-----------------------
inout [DATA_WIDTH-1:0] data_0 ;
inout [DATA_WIDTH-1:0] data_1 ;
//--------------Internal variables----------------
reg [DATA_WIDTH-1:0] data_0_out ;
reg [DATA_WIDTH-1:0] data_1_out ;
reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];
module fifo_tb();
parameter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 3;
reg clk;
wire rst, wr_cs, rd_cs;
wire rd_en, wr_en;
wire [DATA_WIDTH-1:0] data_in ;
wire full, empty;
wire [DATA_WIDTH-1:0] data_out ;
fifo_ports ports (
.clk (clk ),
.rst (rst ),
.wr_cs (wr_cs ),
.rd_cs (rd_cs ),
.rd_en (rd_en ),
.wr_en (wr_en ),
.data_in (data_in ),
.full (full ),
.empty (empty ),
.data_out (data_out)
);
fifo_monitor_ports mports (
.clk (clk ),
.rst (rst ),
.wr_cs (wr_cs ),
.rd_cs (rd_cs ),
.rd_en (rd_en ),
.wr_en (wr_en ),
.data_in (data_in ),
.full (full ),
.empty (empty ),
.data_out (data_out)
);
fifo_top top(ports,mports);
initial begin
$dumpfile("fifo.vcd");
$dumpvars();
clk = 0;
end
endmodule
SystemVerilog Testbench Top
`include "fifo_ports.sv"
initial begin
driver.go();
end
endprogram
FIFO Scoreboard
class fifo_sb;
mailbox fifo = new();
integer size;
function new();
begin
size = 0;
end
endfunction
class fifo_driver;
fifo_sb sb;
virtual fifo_ports ports;
virtual fifo_monitor_ports mports;
bit rdDone;
bit wrDone;
integer wr_cmds;
integer rd_cmds;
task monitorPush();
begin
bit [7:0] data = 0;
while (1) begin
@ (posedge mports.clk);
if (mports.wr_cs== 1 && mports.wr_en== 1) begin
data = mports.data_in;
sb.addItem(data);
$write("%dns : Write posting to scoreboard data = %x\n",$time, data);
end
end
end
endtask
task monitorPop();
begin
bit [7:0] data = 0;
while (1) begin
@ (posedge mports.clk);
if (mports.rd_cs== 1 && mports.rd_en== 1) begin
data = mports.data_out;
$write("%dns : Read posting to scoreboard data = %x\n",$time, data);
sb.compareItem(data);
end
end
end
endtask
task go();
begin
// Assert reset first
reset();
// Start the monitors
repeat (5) @ (posedge ports.clk);
$write("%dns : Starting Pop and Push monitors\n",$time);
fork
monitorPop();
monitorPush();
join_none
$write("%dns : Starting Pop and Push generators\n",$time);
fork
genPush();
genPop();
join_none
task reset();
begin
repeat (5) @ (posedge ports.clk);
$write("%dns : Asserting reset\n",$time);
ports.rst= 1'b1;
// Init all variables
rdDone = 0;
wrDone = 0;
repeat (5) @ (posedge ports.clk);
ports.rst= 1'b0;
$write("%dns : Done asserting reset\n",$time);
end
endtask
task genPush();
begin
bit [7:0] data = 0;
integer i = 0;
for ( i = 0; i < wr_cmds; i++) begin
data = $random();
@ (posedge ports.clk);
while (ports.full== 1'b1) begin
ports.wr_cs = 1'b0;
ports.wr_en = 1'b0;
ports.data_in= 8'b0;
@ (posedge ports.clk);
end
ports.wr_cs = 1'b1;
ports.wr_en = 1'b1;
ports.data_in= data;
end
@ (posedge ports.clk);
ports.wr_cs = 1'b0;
ports.wr_en = 1'b0;
ports.data_in= 8'b0;
repeat (10) @ (posedge ports.clk);
wrDone = 1;
end
endtask
task genPop();
begin
integer i = 0;
for ( i = 0; i < rd_cmds; i++) begin
@ (posedge ports.clk);
while (ports.empty== 1'b1) begin
ports.rd_cs = 1'b0;
ports.rd_en = 1'b0;
@ (posedge ports.clk);
end
ports.rd_cs = 1'b1;
ports.rd_en = 1'b1;
end
@ (posedge ports.clk);
ports.rd_cs = 1'b0;
ports.rd_en = 1'b0;
repeat (10) @ (posedge ports.clk);
rdDone = 1;
end
endtask
endclass
Ports File
`ifndef FIFO_PORTS_SV
`define FIFO_PORTS_SV
interface fifo_ports (
input wire clk ,
output logic rst ,
input wire full ,
input wire empty ,
output logic wr_cs ,
output logic rd_cs ,
output logic rd_en ,
output logic wr_en ,
output logic [7:0] data_in ,
input wire [7:0] data_out
);
endinterface
interface fifo_monitor_ports (
input wire clk ,
input wire rst ,
input wire full ,
input wire empty ,
input wire wr_cs ,
input wire rd_cs ,
input wire rd_en ,
input wire wr_en ,
input wire [7:0] data_in ,
input wire [7:0] data_out
);
endinterface
`endif
Verification Of UART
This testbench will slightly different from what we have seen till now.
TX generator
RX generator
TX monitor
RX monitor
Scoreboard
SystemVerilog testbench top
SystemVerilog Ports file
HDL Testbench top
We are going to have some more components that like reset. Logic to control when to terminate.
//-----------------------------------------------------
// Design Name : uart
// File Name : uart.v
// Function : Simple UART
//
-----------------------------------------------------
module uart (
reset ,
txclk ,
ld_tx_data ,
tx_data ,
tx_enable ,
tx_out ,
tx_empty ,
rxclk ,
uld_rx_data ,
rx_data ,
rx_enable ,
rx_in ,
rx_empty
);
// Port declarations
input reset ;
input txclk ;
input ld_tx_data ;
input [7:0] tx_data ;
input tx_enable ;
output tx_out ;
output tx_empty ;
input rxclk ;
input uld_rx_data ;
output [7:0] rx_data ;
input rx_enable ;
input rx_in ;
output rx_empty ;
// Internal Variables
reg [7:0] tx_reg ;
reg tx_empty ;
reg tx_over_run ;
reg [3:0] tx_cnt ;
reg tx_out ;
reg [7:0] rx_reg ;
reg [7:0] rx_data ;
reg [3:0] rx_sample_cnt ;
reg [3:0] rx_cnt ;
reg rx_frame_err ;
reg rx_over_run ;
reg rx_empty ;
reg rx_d1 ;
reg rx_d2 ;
reg rx_busy ;
// UART RX Logic
always @ (posedge rxclk or posedge reset)
if (reset) begin
rx_reg <= 0;
rx_data <= 0;
rx_sample_cnt <= 0;
rx_cnt <= 0;
rx_frame_err <= 0;
rx_over_run <= 0;
rx_empty <= 1;
rx_d1 <= 1;
rx_d2 <= 1;
rx_busy <= 0;
end else begin
// Synchronize the asynch signal
rx_d1 <= rx_in;
rx_d2 <= rx_d1;
// Uload the rx data
if (uld_rx_data) begin
rx_data <= rx_reg;
rx_empty <= 1;
end
// Receive data only when rx is enabled
if (rx_enable) begin
// Check if just received start of frame
if (!rx_busy && !rx_d2) begin
rx_busy <= 1;
rx_sample_cnt <= 1;
rx_cnt <= 0;
end
// Start of frame detected, Proceed with rest of data
if (rx_busy) begin
rx_sample_cnt <= rx_sample_cnt + 1;
// Logic to sample at middle of data
if (rx_sample_cnt == 7) begin
if ((rx_d2 == 1) && (rx_cnt == 0)) begin
rx_busy <= 0;
end else begin
rx_cnt <= rx_cnt + 1;
// Start storing the rx data
if (rx_cnt > 0 && rx_cnt < 9) begin
rx_reg[rx_cnt - 1] <= rx_d2;
end
if (rx_cnt == 9) begin
rx_busy <= 0;
// Check if End of frame received correctly
if (rx_d2 == 0) begin
rx_frame_err <= 1;
end else begin
rx_empty <= 0;
rx_frame_err <= 0;
// Check if last rx data was not unloaded,
rx_over_run <= (rx_empty) ? 0 : 1;
end
end
end
end
end
end
if (!rx_enable) begin
rx_busy <= 0;
end
end
// UART TX Logic
always @ (posedge txclk or posedge reset)
if (reset) begin
tx_reg <= 0;
tx_empty <= 1;
tx_over_run <= 0;
tx_out <= 1;
tx_cnt <= 0;
end else begin
if (ld_tx_data) begin
if (!tx_empty) begin
tx_over_run <= 0;
end else begin
tx_reg <= tx_data;
tx_empty <= 0;
end
end
if (tx_enable && !tx_empty) begin
tx_cnt <= tx_cnt + 1;
if (tx_cnt == 0) begin
tx_out <= 0;
end
if (tx_cnt > 0 && tx_cnt < 9) begin
tx_out <= tx_reg[tx_cnt -1];
end
if (tx_cnt == 9) begin
tx_out <= 1;
tx_cnt <= 0;
tx_empty <= 1;
end
end
if (!tx_enable) begin
tx_cnt <= 0;
end
end
endmodule
HDL Testbench Top
`include "uart.v"
module top();
wire reset ;
wire ld_tx_data ;
wire [7:0] tx_data ;
wire tx_enable ;
wire tx_out ;
wire tx_empty ;
wire uld_rx_data ;
wire [7:0] rx_data ;
wire rx_enable ;
wire rx_in ;
wire rx_empty ;
wire loopback ;
wire rx_tb_in ;
reg txclk ;
reg rxclk ;
uart_ports ports (
.reset (reset ),
.txclk (txclk ),
.ld_tx_data (ld_tx_data ),
.tx_data (tx_data ),
.tx_enable (tx_enable ),
.tx_out (tx_out ),
.tx_empty (tx_empty ),
.rxclk (rxclk ),
.uld_rx_data (uld_rx_data ),
.rx_data (rx_data ),
.rx_enable (rx_enable ),
.rx_in (rx_in ),
.rx_empty (rx_empty ),
.loopback (loopback )
);
uart_top tbtop(ports);
initial begin
$dumpfile("uart.vcd");
$dumpvars();
txclk = 0;
rxclk = 0;
end
// Loopback control logic
assign rx_in = (loopback) ? tx_out : rx_tb_in;
// RX and TX Clock generation
always #1 rxclk = ~rxclk;
always #16 txclk = ~txclk;
endmodule
SV Testbench Top
`include "uart_ports.sv"
`include "uart_sb.sv"
`include "uart_txgen.sv"
initial begin
fork
txgen.goTxgen();
join_none
UART Scoreboard
class uart_sb;
mailbox tx = new();
mailbox rx = new();
class uart_txgen;
uart_sb sb;
virtual uart_ports ports;
bit tx_done;
bit rx_done;
task txDriver();
begin
integer i = 0;
integer tx_timeout = 0;
bit [7:0] tx_data = 0;
ports.tx_enable = 1;
for (i = 0; i < no_tx_cmds; i ++) begin
tx_data = $random();
sb.txAdd(tx_data);
if (loopback == 1) begin
sb.rxAdd(tx_data);
end
// Check if uart is ready to accept data for transmission
while (ports.tx_empty == 0) begin
@ (posedge ports.txclk);
tx_timeout ++ ;
if (tx_timeout > 10) begin
$write("%dns : txDriver : Warning : tx_empty is 0 for more then 10
clocks\n",
$time);
end
end
tx_timeout = 0;
// Drive the data in UART for transmitting
@ (posedge ports.txclk);
ports.ld_tx_data = 1;
ports.tx_data = tx_data;
$write("%dns : txDriver : Transmitting data %x\n",$time, tx_data);
@ (posedge ports.txclk);
ports.ld_tx_data = 0;
ports.tx_data = 0;
while (ports.tx_empty == 1) begin
@ (posedge ports.txclk);
tx_timeout ++ ;
if (tx_timeout > 10) begin
$write("%dns : txDriver : Warning : tx_empty is 1 for more then 10
clocks\n",
$time);
end
end
tx_timeout = 0;
end
tx_done = 1;
end
endtask
task rxDriver();
begin
bit [7:0] rx_data = 0;
integer i,j = 0;
ports.rx_enable = 1;
if (loopback == 1) begin
ports.loopback = 1;
end else begin
ports.loopback = 0;
for (i = 0; i < no_rx_cmds; i++) begin
rx_data = $random();
sb.rxAdd(rx_data);
$write("%dns : rxDriver : Transmitting data %x\n",$time, rx_data);
@ (posedge ports.txclk);
ports.rx_in = 0;
for (j = 0; j < 8; j ++) begin
@ (posedge ports.txclk);
ports.rx_in = rx_data[j];
end
@ (posedge ports.txclk);
ports.rx_in = 1;
@ (posedge ports.txclk);
end
end
rx_done = 1;
end
endtask
task txMonitor();
begin
bit [7:0] tx_data = 0;
integer i = 0;
while (1) begin
@ (posedge ports.txclk);