0% found this document useful (0 votes)
7 views77 pages

SystemVerilog Notes

The document explains various aspects of SystemVerilog, including the differences between always_comb, always @*, and always_ff procedures, as well as data types like string, logic, void, and user-defined types. It also covers enumerations, arrays, queues, unique-if and priority-if statements, foreach loops, and event control in always blocks. Additionally, it describes fork-join constructs for parallel processing in simulation, highlighting their usage and behavior.

Uploaded by

Asita Sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views77 pages

SystemVerilog Notes

The document explains various aspects of SystemVerilog, including the differences between always_comb, always @*, and always_ff procedures, as well as data types like string, logic, void, and user-defined types. It also covers enumerations, arrays, queues, unique-if and priority-if statements, foreach loops, and event control in always blocks. Additionally, it describes fork-join constructs for parallel processing in simulation, highlighting their usage and behavior.

Uploaded by

Asita Sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 77

always_comb is sensitive to changes within the contents of a function, whereas

always @* is only sensitive to changes to the arguments of a function.


Variables on the left-hand side of assignments within an always_comb
procedure, including variables from the contents of a called function, cannot be
written to by any other processes, whereas always @* permits multiple
processes to write to the same variable.

A always_ff procedure adds a restriction that it can contain one and only one
event control and no blocking timing controls. Variables written on the left-hand
side of assignments within always_ff, including variables from contents of a
called function, cannot be written by other processes.1​

1. DATA TYPES

1.1 String
No need of making reg [8*18-1:0] str1 = “Hello Verilog WOrld”;
--> string s1 = “Hello WOrld”;
--> string s2 = {“Hi“, “! ”, s1};
Format specified = %s

1.2 Logic
logic is the improved version of reg form Verilog to SystemVerilog, so it Can be
driven by continuous assignments, gates, and modules in addition to being a
variable.
It is declared just as reg and wire.

​ 1.3 Void
The void data type represents non-existent data. This type can be specified as the
return type of functions, indicating no return value. Used to indicate no return type in
functions.
--> void’(function_call());

​ 1.4 Event
An event is now a handle to a synchronization object that can be passed around to
routines. In Verilog, if the triggering thread executes before the blocking thread, the
trigger is missed. SystemVerilog introduces triggered function t­­hat lets you check
whether an event has been triggered.
--> event e1;

​ 1.5 User-Defined Data Type


Can be defined with whichever properties using typedef keyword.
--> typedef logic [7:0] byte;
​ byte data

​ 1.6 Class
If class is just kept as a separate construct in SV, like task/functions, we still would
have the
benefits of polymorphism, data abstraction, inheritance etc. But, by making it as a
data type
too, we get to pass objects inside functions and tasks.
--> class MyClass;
​ int data;
​ function new(int val);
​ data = val;
​ endfunction
endclass

module top;
​ MyClass obj;

​ initial begin
​ obj = new(42);
​ pass_object(obj);
​ end

​ task pass_object(MyClass my_obj);


​ $display("Data from object: %d", my_obj.data);
​ endtask
endmodule

2. ENUMERATIONS
An enumerated type defines a set of named values. The simplest enumerated type
declaration contains a list of constant names and one or more variables.

--> enum { red, green, blue, yellow, white, black } Colors;

Here, the actual values are defaulted to integers starting at 0 and then increase. in the
above example by default variable will get the default value of 0,1,2,3,4,5 respectively
from red. The values can be set for the names and also values can be set for some of
the names and not set for other names. A name without a value is automatically
assigned an increment of the value of the previous name.
--> enum { red=0, green, blue=4, yellow, white=10, black } Colors;
In the following example value is set for red = 0, blue = 4, white = 10. green, yellow,
black automatically assigned to the increment-value of 1,5,11 respectively.

If an automatically incremented value is assigned elsewhere in the same enumeration,


this shall be a syntax error. For eg. here -

--> enum { red=0, green=0, blue=4, yellow, white=5, black=6 } Colors

Method Description
first() returns the value of the first member of the enumeration

last() returns the value of the last member of the enumeration

next() returns the value of next member of the enumeration

next(N) returns the value of next Nth member of the enumeration

prev() returns the value of previous 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

Can also use it as typedef:-

--> typedef enum <size> {<value1>, <value2>, ..., <valueN>} <enum_name>;

3. ARRAYS & QUEUES

NOTE:

1.​ Reg [7:0] arr; is not an array. Its a vector.


2.​ Reg arr [7:0]; is not a vector, its an unbound array
3.​ An array can be 1 dimensional only in this above case
4.​ Bound or packed array is >2d and never 1d. 1d is an array.

​ 3.1 Dynamic Arrays


A dynamic array is one dimension of an unpacked array whose size can be set of

changed at run-time.

Dynamic array is Declared using an empty word subscript [ ].

The space for a dynamic array doesn’t exist until the array is explicitly created at
run-time, space is allocated when new[number] is called. the number indicates the
number of space/elements to be allocated.

--> //declaration

bit [7:0] d_array1[ ];

int d_array2[ ];

//memory allocation

d_array1 = new[6]; //dynamic array of 4 elements

d_array1 = new[4]; //new memory allocated, overriding old memory

//array initialization

d_array1 = {0,1,2,3};

foreach(d_array2[j]) d_array2[j] = j;

//array resize

d_array1 = new[10](d_array1);​ //10 new indices’ memory allocated without


overriding the old

//Array Deletion

d_array1.delete;​
If an already allocated dynamic array is done ‘new[n]’ again, then it allocates n new
indices in memory and erase the previous ones.

​ 3.2 Associative Arrays

Associative array Stores entries in a sparse matrix

Associative arrays allocate the storage only when it is used. So, useful when we
have to allocate a large chunk of memory.

Unlike in the dynamic array where we need to allocate memory before using it

An associative array implements a lookup table of the elements of its declared type.
` ` The data type to be used as an index serves as the lookup key and imposes an
ordering.

Use: An associative array implements a lookup table of the elements of its declared `
` type. The data type to be used as an index serves as the lookup key and imposes
an` ` ordering

Declaration Examples:-

--> int a_array1[*] ; // associative array of integer (unspecified index)

--> bit [31:0] a_array2[string]; // associative array of 32-bit, indexed by string


--> ev_array [myClass]; ​ //associative array of event,indexed by class

Method Description

num() returns the number of entries in the associative array

delete(index) removes the entry at the specified index.exa_array.delete(index)

exists(index) returns 1 if an element exists at the specified index else returns 0

first(var) assigns the value of first index to the variable var


last(var) assigns the value of last index to the variable var

next(var) assigns the value of next index to the variable var

prev(var) assigns the value of previous index to the variable var

--> a_array.delete();​ //no args mean whole ass. Array deleted

​ 3.3 Queues

QUEUE DECLARATION-

Data_type queue_name[$];

--> byte queue_3[$:255]; // queue of byte (bounded queue with 256 entries)
--> bit queue_1[$]; // queue of bits (unbound queue)

QUEUE INITIALISATION-

--> queue_1 = {0,1,2,3};

--> queue_4 = {“Red”,"Blue”,"Green”};

Method Description

size() returns the number of items in the queue

insert() inserts the given item at the specified index position

delete() deletes the item at the specified index position

push_front() inserts the given element at the front of the queue


push_back() inserts the given element at the end of the queue

pop_front() removes and returns the first element of the queue

pop_back() removes and returns the last element of the queue

The only difference between working of bounded and unbounded queue is that when all
the elements of bounded queue are filled, then when we push_front(), the last element
will be popped back, everything will be shifted and the front element will store the given
value. Similarly, when we do push_back() in bounded queue, then the first element will
be popped out.

4. UNIQUE-IF & PRIORITY-IF STATEMENTS

Evaluates all conditions at the same time, parallely and gives warning for 2 situations:
(i) >1 Conditions are true

(ii) No condition is true & doesn’t have else

--> module unique_if;

//variables declaration

int a,b,c;

initial begin

//initialization

a=10;

b=20;

c=40;

unique if ( a < b ) $display("\t a is less than b");

else if ( a < c ) $display("\t a is less than c");

else $display("\t a is greater than b and c");

end

endmodule

OUTPUT:

a is less than b

RT Warning: More than one conditions match in 'unique if' statement.

--> module unique_if;

​ //variables declaration

​ int a,b,c;

​ initial begin
​ //initialization

a=50;

b=20;

c=40;

unique if ( a < b ) $display("\t a is less than b");

else if ( a < c ) $display("\t a is less than c");

end

endmodule

OUTPUT:

RT Warning: No condition matches in ‘unique if’ statement

If the first example if done with ‘priority’ keyword rather than ‘unique’, then it will also
work parallely and on top of that, the statement with priority if statement will be priorities
and executed without ay warning, now, even though 2 conditions are true
simultaneously.

--> module unique_if;

​ //variables declaration

​ int a,b,c;

​ initial begin

​ //initialization

a=10;

​ b=20;

​ c=40;

​ priority if ( a < b ) $display("\t a is less than b");

​ else if ( a < c ) $display("\t a is less than c");


​ else $display("a is greater than b & c");

end

endmodule

OUTPUT:

​ a is less than b

5. FOREACH LOOP

Used for easier operations on arrays.

--> module for_loop;

int a[4];

initial begin

$display("-----------------------------------------------------------------");

foreach(a[i]) a[i] = i;

foreach(a[i]) $display("\tValue of a[%0d]=%0d",i,a[i]);

$display("-----------------------------------------------------------------");

end

Endmodule

This can also be done with multi-dimensional arrays.

--> module for_loop;

int a[3][2];

initial begin
$display("-----------------------------------------------------------------");

foreach(a[i,j]) a[i][j] = i+j;

foreach(a[i,j]) $display("\tValue of a[%0d][%0d]=%0d",i,j,a[i][j]);

$display("-----------------------------------------------------------------");

end

endmodule

6. BREAK & CONTINUE STATEMENTS

Break statement ends the whole loop, while continue statement ends 1 iteration of the
loop.

7. EVENT CONTROL IN ALWAYS BLOCK

​ 7.1 ‘or’ keyword

--> module event_ctrl;

bit clk;
bit rst

always #2 clk = ~clk;

//always block will be executed at every negedge of clk signal

always @(negedge clk or posedge rst)

begin

$display($time,"\tInside always block");

end

initial begin

#20 $finish;

end

Endmodule

Here, the always block will be executed at every negedge of clk or posedge of rst.

​ 7.2 ‘iff’ keyword in event control

--> module event_ctrl;

bit clk;

bit reset;

always #2 clk = ~clk;

//at posedge of clk if reset is equals to '0',always block will be executed

always @(posedge clk iff reset == 0)

begin :block-1

$display($time,"\tInside always block");


end :block-1

//always block will be executed at every posedge and negedge of clk signal

always @(posedge reset or negedge reset)

begin :block-2

$display($time,"\tReset Value = %0d",reset);

end :block-2

initial begin

#40 $finish;

end

initial begin

reset = 1;

#7 reset = 0;

#8 reset = 1;

#5 reset = 0;

end

endmodule

8. FORK-JOIN

Used to simulate parallel processing of processors by executing >=1 blocks parallely


inside a sequentially executing procedural blocks(always or initial).

​ 8.1 fork-join​
STEP-1​ ​ ​ ​ STEP-2​ ​ ​ ​ STEP-3

--> module fork_join;

initial begin

$display("-----------------------------------------------------------------");

Fork
//-------------------

//Process-1

//-------------------

Begin

$display($time,"\tProcess-1 Started");

#5;

$display($time,"\tProcess-1 Finished");

end

//-------------------

//Process-2

//-------------------

Begin

$display($time,"\tProcess-2 Started");

#20;

$display($time,"\tProcess-2 Finished");

End

Join

$display($time,"\tOutside Fork-Join");

$display("-----------------------------------------------------------------");

$finish;

​ End

endmodule

OUTPUT:
​ -----------------------------------------------------------------

0 Process-1 Started

0 Process-2 Startedt

5 Process-1 Finished

20 Process-2 Finished

20 Outside Fork-Join

-----------------------------------------------------------------

​ 8.2 fork-join_any
--> module fork_join;

initial begin

​ $display("-----------------------------------------------------------------");

Fork

//Process-1

Begin

$display($time,"\tProcess-1 Started");

#5;

$display($time,"\tProcess-1 Finished");

End

//Process-2

Begin
$display($time,"\tProcess-2 Started");

#20;

$display($time,"\tProcess-2 Finished");

End

join_any

$display($time,"\tOutside Fork-Join");

$display("-----------------------------------------------------------------");

End

endmodule

OUTPUT:

​ --------------------------------------------------------

0 Thread-1 Started

0 Thread-2 Started

5 Thread-1 Finished

5 Outside Fork-Join

-----------------------------------------------------------------

20 Process-2 Finished

​ 8.3 fork-join_none
--> module fork_join_none;

initial begin

$display("-----------------------------------------------------------------");

fork

//Process-1

Begin

$display($time,"\tProcess-1 Started");

#5;

$display($time,"\tProcess-1 Finished");

End

//Process-2

Begin

$display($time,"\tProcess-2 Started");

#20;

$display($time,"\tProcess-2 Finished");

End

join_none

$display($time,"\tOutside Fork-Join_none");

$display("-----------------------------------------------------------------");

end
endmodule

​ 8.4 wait fork

If there is $finish statement after fork-join-none/any is $finish, then the parallel blocks
will not ` ` be executed and the process will be terminated before their execution.

--> module wait_fork;

initial begin

$display("-----------------------------------------------------------------");

Fork

//Process-1

Begin

$display($time,"\tProcess-1 Started");

#5;

$display($time,"\tProcess-1 Finished");

End

//Process-2

Begin

$display($time,"\tProcess-2 Started");

#20;

$display($time,"\tProcess-2 Finished");

End

Join_any

wait fork; //waiting for the completion of active fork threads

$display("-----------------------------------------------------------------");
$finish; //ends the simulation

End

endmodule

​ 8.5 disable fork

causes the process to kill/terminate all the active processes started from fork blocks.

--> module disable_fork;

initial begin

$display("-----------------------------------------------------------------");

Fork

//Process-1

Begin

$display($time,"\tProcess-1 of fork-1 Started");

#5;

$display($time,"\tProcess-1 of fork-1 Finished");

End

//Process-2

Begin

sub_process();

End

join_any

disable fork;

$display("-----------------------------------------------------------------");

$display($time,"\tAfter disable-fork");
$display("-----------------------------------------------------------------");

End

//Sub-Process

task sub_process;

$display($time,"\tSub-Process Started");

#10;

$display($time,"\tSub-Process Finished");

endtask

endmodule

9. CLASSES

●​ Every class has its own default new() constructor and is used, until you explicitly
define a new() function
●​ A base class handle can point a child class handle, but a child class handle can’t
point a base class handle which is pointing to base class object. If that base
class handle already points to a child class object, then we can assign it to a new
child class handle using dynamic casting.
●​ Static Vars
○​ All properties allocate fresh set of memory for each object. If we wish to
allocate the memory only once and all the objects access the same mem.,
we use ‘static’ keyword.
○​ We can initialise static var without new() function of object handle.
○​ Static metaphors work the same and cannot access non-static vars, only
static vars.

-->Class Template Code

// Code your design here


class packet;

//static members

static int width;

static byte pkts_created;​ //number of pkts created

//class properties

bit [31:0] addr;

bit [31:0] data;

bit write;

string pkt_type;

byte pkt_id;

//constructor

function new(bit [31:0] addr,data,bit write,string pkt_type);

this.addr = addr;

this.data = data;

this.write = write;

this.pkt_type = pkt_type;

this.pkt_id = pkts_created;

width = 8;

pkts_created++;

endfunction

//Static method to display number of


static function void display_packets_created();

$display("--------------------------------------");

$display("\t %0d packets created.",pkts_created);

$display("--------------------------------------");

endfunction

//method to display class properties

function void display();

$display("---------------------------------------------------------");

$display("\t addr = %0h",addr);

$display("\t data = %0h",data);

$display("\t write = %0h",write);

$display("\t pkt_type = %0s",pkt_type);

$display("\t data width = %0d", width);

$display("\t packet id = %0d", pkt_id);

$display("---------------------------------------------------------");

endfunction

endclass

module sv_constructor;

packet pkt[3];

initial begin

//MEMORY ALLOCATION OF PACKET CLASS OBJECTS


pkt[0] = new(32'h10,32'hFF,1,"GOOD_PKT");

Packet::width = 32’h00000008;​ //can also be accessed through . operator


using handle

//scope resolution operator(above)

pkt[0].display_packets_created();

pkt[1] = new(32'h10,32'hFF,1,"BAD_PKT");

pkt[1].display_packets_created();

//CLASS ASSIGNMENT

pkt[2] = pkt[1];​ // can also be cone with another handle, other than the array

foreach(pkt[i]) begin

pkt[i].display();

end

end

Endmodule

-->Shallow Copy
Its Limitations:

→ Deep Copy
●​ Classes can also have parameters. These parameters can also have data types:

--> class packet #(parameter type T = int);

T address;

T data ;

function new();

address = 10;

data = 20;

endfunction

endclass

Packet pkt1;​ //data type is int

Packet #(bit [31:0]) pkt2;​ //data type is 32-bit

​ 9.1 Inheritance

●​ A class can either inherit the whole class using ‘extends’ keyword, or just make a
class handle of the class to be inherited inside the child class.

--> 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;

$display("Value of addr = %0d data = %0d",c.addr,c.data);

end

endmodule

--> class ext_class_1 extends base_class;

function void display();

$display("Inside extended class 1");


endfunction

endclass

class ext_class_2 extends base_class;

function void display();

$display("Inside extended class 2");

endfunction

endclass

class ext_class_3 extends base_class;

function void display();

$display("Inside extended class 3");

endfunction

endclass

ext_class_1 ec_1 = new();

ext_class_2 ec_2 = new();

ext_class_3 ec_3 = new();

base_class b_c[3];

b_c[0] = ec_1;

b_c[1] = ec_2;

b_c[2] = ec_3;

b_c[0].display();

b_c[1].display();

b_c[2].display();
OUTPUT:

Inside extended class 1

Inside extended class 2

Inside extended class 3

●​ Normally, when I call the display function through the child class’s object, the
display function of the child class will be executed, even if the base class also
has a display function.
●​ To also get the base class’s display function executed, we use ‘super’ keyword
inside the display function of the child class. Because at that time, it is not bound
to any object, so no early binding. So, using super keyword, we specify that the
base class’s function is to be called.

--> 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();

super.display();

$display("Data = %0d",data);

endfunction

endclass
module inheritance;

initial begin

child_class c=new();

c.addr = 10;

c.data = 20;

c.display();

end

endmodule

●​ Casting
○​ Def: SystemVerilog casting means the conversion of one data type to
another datatype.
○​ Uses: During value or variable assignment to a variable, it is required to
assign value or variable of the same data type. Some situations need
assignment of different data type
○​ Static Casting
■​ Not applicable to OOP
■​ Here, the data type converted to is fixed at compile-time only.
■​ Uses a cast(‘) operator with the val./expression/var enclosed in
brackets - ()
■​ Int_a = int’(2.1 * 3.2);
○​ Dynamic Casting
■​ Dynamic casting is used to, safely cast a super-class pointer
(reference) into a subclass pointer (reference) in a class hierarchy
■​ Compatibility checked during runtime.
■​ Done using ‘$cast(destination, source)’ syntax

​ ​ --> 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();

super.display();

$display("Data = %0d",data);

Endfunction

Endclass

module inheritence;

initial begin

parent_class p;

child_class c=new();

child_class c1;

c.addr = 10;wait I

c.data = 20;

p = c; //p is pointing to child class handle c.

$cast(c1,p); //with the use of $cast, type chek will occur during
runtime

c1.display();

end

endmodule

●​ Access Modifiers(AM)
○​ Has only 2, cuz no AM means public.
○​ The keywords, local and protected need to be given individually to every
member/method like this:

local int nb;

protected bit function chk(int num);

○​ Protected members can be accessed by that own class and its derived
class. While, local members can only be accessed by that own class.
●​ ABstract CLass: An abstract class can be derived/inherited, but not instantiated.

--> virtual class packet;

bit [31:0] addr;

endclass

class extended_packet extends packet;

function void display;

$display("Value of addr is %0d", addr);

Endfunction

endclass

module virtual_class;

initial begin

extended_packet p;

p = new();

p.addr = 10;

p.display();

end

Endmodule
●​ SImilarly, we can make virtual methods also. SO when a base class handle
pointing to a derived class object calls display function(present in both classes),
will execute the display function of derived class, if base class’s function is virtual.
●​ External Functions: A method part of class, but defined outside the class using
scope resolution operator, although declared inside the class only, using ‘extern’
keyword.

--> class packet;

bit [31:0] addr;

bit [31:0] data;

//function declaration - extern indicates out-of-body declaration

extern virtual function void display();

Endclass

//function implementation outside class body

function void packet::display();

$display("Addr = %0d Data = %0d",addr,data);

Endfunction

module extern_method;

initial begin

packet p;

p = new();

p.addr = 10;

p.data = 20;

p.display();

end
Endmodule

●​ To declare a class before its definition, we use ‘typedef’ keyword.

--> typedef class c2;

//class-1

class c1;

c2 c; //using class c2 handle before declaring it.

endclass

//class-2

class c2;

c1 c;

Endclass

module typedef_class;

initial begin

c1 class1;

c2 class2;

$display("Inside typedef_class");

End

Endmodule

10. INTERPROCESS COMMUNICATION


​ 10.1 Events

An event is a way to synchronize two or more different processes. One process waits
for the` ` ` event to happen while another process triggers the event.

--> event eventA​ //Declaration

The declaration of an event is done just like a datatype, outside the procedural
blocks.

--> -> eventA​ //triggering of eventA

This is done inside a procedural block.

--> @eventA;

--> wait(eventA.triggered);

Both are valid ways to wait until the event is triggered. This is done in another
procedural block

Example Code:

--> module tb_top; ​event eventA; // Declare an event handle called


"eventA" ​ initial begin ​ ​
` fork ​ ​ ​

waitForTrigger (eventA); // Task waits for eventA to


happen ​ ​ ​ #5 ->eventA; // Triggers eventA ​ ​
` join ​
end ​ // The event is passed as an argument to this task. It
simply waits for the event to be triggered
​ task waitForTrigger (event eventA); ​ ​
` ` $display ("[%0t] Waiting for EventA to be triggered",
$time); ​ ​ ` ` wait (eventA.triggered); ​ ​
` ` ` ` $display ("[%0t] EventA has triggered", $time); ​
endtask endmodule

​ 10.2 Semaphores

Semaphores are event-base gates to give access of something to different processes.


They are mutually exclusive(mutex) i.e it only gives access to one process at a time.

Due to the mutually exclusivity, Semaphores are best for basic synchronisation and
access control to shared resources.
A Semaphore is a built-in class, so is used just like another class, by making its object
and calling its functions.

Semaphore has 4 methods:

Name Description

function new (int keyCount = 0); Specifies number of keys initially allocated to the semaphore bucket

function void put (int keyCount = 1); Specifies the number of keys being returned to the semaphore

task get (int keyCount = 1); Specifies the number of keys to obtain from the semaphore

function int try_get (int keyCount = 1); Specifies the required number of keys to obtain from the semaphore

--> module tb_top; semaphore key; ​ ​ ​ ​ // Create a


semaphore handle called "key"

initial begin key = new (1); ​ ​ ​ // Create only a


single key; multiple keys are also possible

fork personA (); ​ ​ ​ // personA tries to get the


room and puts it back after work

personB (); ​ ​ ​ // personB also tries to get the room


and puts it back after work

#25 personA (); ​ ​ // personA tries to get the room a


second time

join_none
end

task getRoom (bit [1:0] id);

$display ("[%0t] Trying to get a room for id[%0d] ...",


$time, id);

key.get(1);

$display ("[%0t] Room Key retrieved for id[%0d]", $time, id);

endtask

task putRoom (bit [1:0] id);

$display ("[%0t] Leaving room id[%0d] ...", $time, id);


key.put (1);

$display ("[%0t] Room Key put back id[%0d]", $time, id);

endtask

// This person tries to get the room immediately and puts // it


back 20 time units later task

personA ();

getRoom (1);

#20 putRoom (1);

endtask

// This person tries to get the room after 5 time units and
puts it back after // 10 time units

task personB ();

#5 getRoom (2);

#10 putRoom (2);

endtask

endmodule

OUTPUT:
[0] Trying to get a room for id[1] ...

[0] Room Key retrieved for id[1]

[5] Trying to get a room for id[2] ...

[20] Leaving room id[1] ...

[20] Room Key put back id[1]

[20] Room Key retrieved for id[2]

[25] Trying to get a room for id[1] ...

[30] Leaving room id[2] ...

[30] Room Key put back id[2]

[30] Room Key retrieved for id[1]

[50] Leaving room id[1] ...

[50] Room Key put back id[1]

​ 10.3 Mailbox

A mailbox is like a dedicated channel established to send data between two


components.

It is also a built-in class and so, used like a class.

SystemVerilog mailboxes are created as having either a bounded or unbounded


queue size. A bounded mailbox can only store a limited amount of data, and if a
process attempts to store more messages into a full mailbox, it will be suspended until
there's enough room in the mailbox. However, an unbounded mailbox has unlimited
size.

A mailbox acts behaves like a queue, but differentiates on two points:

1.​ Mailbox also uses semaphores to synchronize the push & pop.
2.​ We can’t access a specific index within the mailbox queue.

A SystemVerilog mailbox is typically used when there are multiple threads running in
parallel… ` and want to share data for which a certain level of determinism is required.
module tb; ​ // Create a new mailbox that can hold utmost 2 items ​
mailbox ​ mbx = new(2); ​

// Block1: This block keeps putting items into the mailbox ​

// The rate of items being put into the mailbox is 1 every ns ​

initial begin ​ ​

for(int i=0; i < 5; i++) begin ​

#1 mbx.put (i); ​

$display ("[%0t] Thread0: Put item #%0d, size=%0d",


$time, i, mbx.num()); ​

end

end ​

// Block2: This block keeps getting items from the mailbox ​

// The rate of items received from the mailbox is 2 every ns ​

initial begin ​

forever begin ​ ​ ​

int idx; ​ ​ ​

#2 mbx.get (idx); ​

$display ("[%0t] Thread1: Got item #%0d, size=%0d", $time,


idx, mbx.num()); ​ ​

end ​

end

endmodule

There are 8 methods for mailbox:-

Function Description
Returns a mailbox handle, bound > 0 represents size of
function new (int bound = 0);
mailbox queue

Returns the number of messages currently in the


function int num ();
mailbox

Blocking method that stores a message in the mailbox in


task put (singular message);
FIFO order; message is any singular expression

Non-blocking method that stores a message if the


function int try_put (singular
mailbox is not full, returns a postive integer if
message);
successful else 0

task get (ref singular Blocking method until it can retrieve one message from
message); the mailbox, if empty blocks the process

function int try_get (ref Non-blocking method which tries to get one message
singular message); from the mailbox, returns 0 if empty

task peek (ref singular Copies one message from the mailbox without
message); removing the message from the mailbox queue.

function int try_peek (ref Tries to copy one message from the mailbox without
singular message); removing the message from queue
By default, mailbox is typeless i.e it can send and receive objects of mixed datatypes.
This option came handy, but it led to mismatches during simulation time. So, mailbox
can also be parameterized with a data type name and that mailbox will only get and put
a specific fixed data type. This is called a parameterized mailbox. Before using it, its
handle is made globally:

--> Typedef mailbox #(string) s_mbox;

And then, wherever needed, the handle name, s_mbox is instantiated instead of
mailbox(done previously).

11. INTERFACE

An interface is a set of signals or ports that can be used to connect between different
modules. Specifically, they are a set of signals, based on the ports of DUT. They directly
connect to DUT ports. And then their bi-directional ports are connected to other
modules, mainly testbench modules, so that they’re indirectly connected to the DUT.

Advantage: This indirect connection to DUT helps keep the testbench environment
immune to the change of pinsin DUT, as the change has to be done only in the
interface.

Syntax:-

--> interface myBus (input clk);

logic [7:0] data;

logic enable;

modport TB (input data, clk, output enable);

modport DUT (output data, input enable, clk); // From DUT


perspective, 'data' is output and 'enable' is input

endinterface

module dut (myBus busIf);

always @ (posedge busIf.clk)


if (busIf.enable)

busIf.data <= busIf.data+1;

else

busIf.data <= 0;

endmodule // Filename : tb_top.sv

module tb_top;

bit clk; // Create a clock

always #10 clk = ~clk; // Create an interface object

myBus busIf (clk); // Instantiate the DUT; pass modport DUT of


busIf

dut dut0 (busIf.DUT); // Testbench code : let's wiggle enable

initial begin

busIf.enable <= 0;

#10 busIf.enable <= 1;

#40 busIf.enable <= 0;

#20 busIf.enable <= 1;

#100

$finish;

end

endmodule

The pins are declared as ‘logic’. This data type lets you drive signals via assign
statements and store value i.e it acts as both wire & reg. This comes handy to connect
the reg of tb and wire of DUT.

In order to define the direction of ports, the signals’ direction is defined from the tb and
DUT’s POV.
modport TB (input data, clk, output enable);

modport DUT (output data, input enable, clk);

Modports also help to select a specific set of interface signals that can be accessed by
a specifc module.

Here’s an example:

--> interface intf #(int idx = 10)(input logic clk, clr);

​ logic [idx-1:0] addr_vi;

​ logic wr_en_vi;

​ logic rd_en_vi;

​ logic [7:0] wdata_vi;

​ logic [7:0] rdata_vi;

endinterface

//TESTBENCH CODE

//Interface connection

intf #(index) i_intf (clk, clr);

//DUT connection

memory #(size, index) SRAM(

​ .clk(i_intf.clk),

​ .clr(i_intf.clr),

​ .addr(i_intf.addr_vi),

​ .wr_en(i_intf.wr_en_vi),

​ .rd_en(i_intf.rd_en_vi),

.wdata(i_intf.wdata_vi),

​ .rdata(i_intf.rdata_vi)
);

To specify any timing requirements or synchronisation schemes(which is not specified


by modports/intf), a Clocking Block is used.

In a level-1 testbench, where we had to give stimulus input and had to sychronize the
inputs manually and that was done just before the posedge or the event where the
value reflects at the output. Clocking block automates the process, by acknowledging
the triggering event and then, we just change value at that event only through some
syntax.

Now, our DUT is also a sequential circuit only. So, it also needs sequentially at
posedge. So, this clocking block that could have been used for a level-1 testbench, is
also specified inside the interface as a property. This clocking block is used by tb and
DUT to drive/sample values to interface. Then, the interface handles the values
sequentially.

Also, this approach also removes the problem of glitches in sending stimulus. And
without this, the asynchronous flow of data could lead to race conditions.

Interface:

--> interface bus_if(input logic clk);

logic enable, write, read, data_in, data_out;

// Clocking block for signal synchronization

clocking cb @(posedge clk);

​ Default input #1 output #2​ //input sampled 1 ns before the posedge & output is
driven 2 ns ` ​ ​ ​ ​ ​ after the posedge

output data_out;

input enable, write, read, data_in;

endclocking

endinterface

A clocking block consists of:-


1.​ Clocking event: the specific event for data flow is specified i.e posedge of clk
2.​ Clocking Signals: inputs and outputs are specified w.r.t to Testbench
3.​ Clocking Skew: default skews for input & output.

Testbench:

--> module tb_with_interface;

logic clk;

bus_if b(clk); // Instantiate interface with clock

initial begin

clk = 0;

forever #5 clk = ~clk; // Clock generation

end

initial begin

b.cb.enable <= 0;

b.cb.write <= 0;

b.cb.read <= 0;

b.cb.data_in <= 0;

// Synchronize with clocking block

@(b.cb); // Wait for clock edge

b.cb.enable <= 1;

b.cb.write <= 1;

b.cb.data_in <= 1;

@(b.cb); // Next clock cycle

b.cb.write <= 0;
b.cb.read <= 1;

@(b.cb);

$display("Data Out: %b at time %0t", b.cb.data_out, $time);

end

endmodule

A clocking block can be multiple in a module, but 1 clock can only have 1 clocking
block.

Here, the clocking block, cb is used through the instance made of interface as event
and values are changed accordingly.

DUT:

--> module dut(bus_if b);

always_ff @(posedge b.clk) begin

if (b.enable) begin

if (b.write) b.data_out <= b.data_in;

else if (b.read) b.data_out <= ~b.data_out;

end

end

endmodule

DUT also accesses the clocking block like tb, receives and sends data to the
interface synchronously.

The manual form to induce input skew is:

--> module des (output reg[3:0] gnt);

always #1 gnt <= $random;


endmodule

10. Randomisation

SystemVerilog randomization is the process of generating random values to a variable.


$random can also generate random integer values, but the randomisation concept also
provides range or distribution for the occurrence of numbers.

To randomise in a class, we first declare a var as ‘rand’ inside the packet class:

--> rand bit wr_rd;

As some inputs might be invalid for the DUT, if the var is randomized from 0 to infinite,
So, to make the randomisation within a user-defined bracket, we call it constraints and
the process if constrained randomisation:

--> randc bit wr_rd;

And then, the object of this packet class is randomised using a built-in function, inside a
generator class, or a module.

--> pkt.randomize();

Note that the whole object is randomized, but only those vars where we have written
‘rand’/’randc’ will be randomized.

Now, for selective randomisation i.e to randomize one var of the object, but not the
others, we use rand_mode() function.

--> pkt.var1.rand_mode(0);​​ //to specifically stop var1’s randomisation

--> pkt.var2.rand_mode(1);​​ //to specifically start var2’s randomisation

❖​ rand_mode() is to be used before we do packet.randomize()

How to use rand_mode(0):- To disable randomisation of a var, we need to set


rand_mode() as 0 before we use randomize() function. As, its execution is just randomly
allocating 1 value to the var. If its executed, that means a random value to all the rand
vars is already given. It means nothing to disable randomisation of that var after it.

--> pkt.var1.rand_mode(0);

pkt.randomize();

How to use rand_mode(1):- This activates a particular signal, but we still have to use
packet.randomize() function. Also, if I have 3 signals var1, var2, var3 and we only want
to randomize var1, we can’t directly activate var1 using rand_mode(1). Instead, we
deactivate var2 & var3 and then randomize. rand_mode(1) is just used to reactivate the
already deactivated signal. For eg. if we disable randomisation for var2 & var3 and then
do randomisation, then if again randomize the signal but this time for var1 & var2, so I
activate var2’s randomisation. Here’s the code for the example:

--> pkt.var2.rand_mode(0);

pkt.var3.rand_mode(0);

pkt.randomize();

$display(....);

pkt.var2.rand_mode(1);

pkt.randomize();

$display(....);

pre_randomize() & post_randomize():

If we have to perform some operations before or after randomisation, we can make


pre_randomize(); & post_randomize(); methods.

These methods will be called before & after the randomization respectively.

--> class packet;

rand bit [7:0] addr;

randc bit wr_rd;

bit tmp_wr_rd;
//pre randomization function - disabling randomization of
addr,

//if the prevoius operation is write.

function void pre_randomize();

if(tmp_wr_rd==1) addr.rand_mode(0);

else addr.rand_mode(1);

endfunction

//post randomization function - store the wr_rd value to


tmp_wr_rd

//and display randomized values of addr and wr_rd

function void post_randomize();

tmp_wr_rd = wr_rd;

$display("POST_RANDOMIZATION:: Addr = %0h,wr_rd =


%0h",addr,wr_rd);

endfunction

endclass

module rand_methods;

initial begin

packet pkt;

pkt = new();

repeat(4) pkt.randomize();

end

endmodule
Constraints

Now, to perform constrained randomisation(the one done with randc vars) we define
constraint blocks, which can define the range of randomisation in several ways.

The syntax of constraint block is:

constraint <constraint_block_name> { <condition/expression>;

...

<condition/expression>; }

Eg.- constraint addr_range { addr > 5; }

The constraint block is defined in the packet class only, after the rand vars
declaration.

To define it outside the class:

class packet;

rand bit [3:0] addr;

//constraint block declaration

constraint addr_range;

endclass

//constraint implementation outside class body

constraint packet::addr_range { addr > 5; }

There is also polymorphism in constraints.

--> class packet;

rand bit [3:0] addr;

constraint addr_range { addr > 5; }


endclass

class packet2 extends packet;

constraint addr_range { addr < 5; } //overriding constraint of


parent class

endclass

module const_inhe;

initial begin

packet pkt1;

packet2 pkt2;

pkt1 = new();

pkt2 = new();

$display("------------------------------------");

repeat(5) begin

pkt1.randomize();

$display("\tpkt1:: addr = %0d",pkt1.addr);

end

$display("------------------------------------");

repeat(5) begin

pkt2.randomize();
$display("\tpkt2:: addr = %0d",pkt2.addr);

end

$display("------------------------------------");

end

endmodule

These are the various ways to write a constraint block:-

1.​ constraint addr_range { addr inside { [5:10]}; } //5 to 10


range
2.​ constraint addr_range { addr inside { 1,3,5,7,9}; } //only
1, 3, 5,7,9 specifically
3.​ constraint addr_range { addr inside
{1,3,[5:10],12,[13:15]}; } //fusion of range([]) and
specific indices(1,2..)
4.​ constraint addr_range { addr !(inside {[5:10]});} //not 5
to 10
5.​ constraint addr_range { addr inside
{[start_addr:end_addr]};} //the range can also be specified
through a variable.
6.​ Constraint addr_range { addr < 5 }//from 0 to 4. Similarly
we can use <, >, <=, <=, == & != operators

After specifying a range, every element has an equal probability to occur. Weighted
distribution can also be added to specify the occurrence of a particular part of the range.

There are 2 types of weighted distributions:

●​ := operator - assigns the specified weight to the item, or if the item is a range,
specified weight to every value in the range.

​ --> constraint addr_range{addr dist { 2 := 5, [10:12] := 8


};}
​ Here, the value addr = 2 has a weight of 5

​ ​ ​ ​ Addr = 10 has a weight of 8

​ ​ ​ ​ Addr = 11 has a weight of 8

​ ​ ​ ​ Addr = 12 has a weight of 8 i.e for :=


operator, the weight assigned is given to each number of the
range.

●​ /= operator - assigns the specified weight to the item, or if the item is a range,
specified weight/n to every value in the range. where n is the number of values in
the range.

​ --> constraint addr_range{addr dist { 2 :/ 5, [10:12] :/ 8


};}

​ Here, the value addr = 2 has a weight of 5

​ ​ ​ ​ Addr = 10 has a weight of 8/3

​ ​ ​ ​ Addr = 11 has a weight of 8/3

​ ​ ​ ​ Addr = 12 has a weight of 8/3

Observational Note:- If I give dist{[0:3] := 1}; then every iteration will result in the
same set of values chosen/randomized. And if we do dist{[0:3] :/ 4}; in place of
that, then as the individual value’s weight assigned is the same, both will give the
same set of values as output.

Implication Constraint:- Now, if a particular signal needs a set of several


constraints at different cases, each constraint can be encoded with a separate
string.

--> // Code your design here

class packet;

rand bit [3:0] addr;


string addr_range;​ //parameter based on which 1
constraint block is chosen.

constraint address_range { (addr_range == "small") -> (addr <


8);} //used if the string value given in module will be “small”

constraint address_range2 { (addr_range == "large") -> (addr >


8);} //used if the string value given in module will be “large”

constraint address_range3 { (addr_range == "medium") -> (addr


== 8);} //used if the string value given in module will be
“medium”

endclass

module constr_implication;

initial begin

packet pkt;

pkt = new();

pkt.addr_range = "small";​//constraint is chosen based on


the value given here

$display("------------------------------------");

repeat(4) begin

pkt.randomize();

$display("\taddr_range = %s addr =
%0d",pkt.addr_range,pkt.addr);

end

$display("------------------------------------");

end
endmodule

Instead of having multiple constraints, we can also give if else conditions for the string
parameter in 1 constraint only.

--> class packet;

rand bit [3:0] addr;

string addr_range;

constraint address_range { if(addr_range == "small")

addr < 8;

else if(addr_range == "large")

addr > 8;

else if(addr_range == "medium")

​ addr == 8;

else

​ addr != 8;

endclass

module constr_if_else;

initial begin

packet pkt;

pkt = new();

pkt.addr_range = "small";
$display("------------------------------------");

repeat(3) begin

pkt.randomize();

$display("\taddr_range = %s addr =
%0d",pkt.addr_range,pkt.addr);

end

$display("------------------------------------");

pkt.addr_range = "high";

$display("------------------------------------");

repeat(3) begin

pkt.randomize();

$display("\taddr_range = %s addr =
%0d",pkt.addr_range,pkt.addr);

end

$display("------------------------------------");

end

endmodule

If the rand signal is an unbound array, then we can also give a constraint for each index
using foreach loop inside the constraint block.

--> class packet;

rand byte addr [];

rand byte data [];

constraint avalues { foreach( addr[i] ) //

​​ ​ ​ if(i == 0)

addr[i] inside {3,6,9,12};


else if(i <= 6)

addr[i] inside{4,8,12,16};

else

addr[i] inside{5,10,15,20};}

constraint dvalues { foreach( data[j] ) data[j] > 4 * j; }

constraint asize { addr.size < 9; }

constraint dsize { data.size == addr.size; }

endclass

module constr_iteration;

initial begin

packet pkt;

pkt = new();

$display("------------------------------------");

repeat(2) begin

pkt.randomize();

$display("\taddr-size = %0d data-size =


%0d",pkt.addr.size(),pkt.data.size());

foreach(pkt.addr[i]) $display("\taddr = %0d data =


%0d",pkt.addr[i],pkt.data[i]);

$display("------------------------------------");

end

end
endmodule

This is the only way to iteratively specify constraint for each index. Otherwise, if we only
want to specify a constraint for 1 index, then we can just specify the index in the
constraint without foreach loop.

constraint_mode():- To disable or enable the constraint block, we can use


constraint_mode(0) and constraint_mode(1) respectively.

As there can be multiple constraints in one class, we specify the constraint name we
have set the mode for. Just like in rand_mode().

constraint_mode() function works exactly like rand_mode(). So, constraint_mode(0) is


used before the randomisation, and if we need to randomise again with the constraints,
we set constraint_mode(1) before the 2nd randomisation.

Static constraint:- Just like other class properties, a constraint of class can also be
made static. And with that, each handle can access the same constraint. Due to its
rules, the features of static constraint is:-

1.​ Fixed nature: The conditions in static constraints are constant and do not
depend on runtime values or dynamic changes.
2.​ Deterministic: They always apply in the same way regardless of any external
factors or randomization seeds.
3.​ Simple and efficient: Since the constraints are static, they are computationally
simpler and lead to predictable behavior during randomization.

Static v/s Non-static Constraints

1.​ Disabling static constraints


●​ Now, its the same constraint(addr == 5) block for all handles. So lets say
there are 2 separate objects.

​ ​ --> packet pkt1;

​​ packet pkt2;

●​ Then disabling the constraint using constraint_mode() from 1 handle


​ --> pkt2.addr_range.constraint_mode(0);

●​ That makes it disabled when accessed from other objects, too.

--> pkt1.randomize();

$display();

pkt2.randomize();

$display();

output: pkt1.addr = 124

​ pkt2.addr = 146

2.​ Disabling non-static constraints


●​ Now, a non static constraint(addr == 5;) has built a separate copy for a
separate object. Thus, 2 objects, pkt1, pkt2 have separate constraints.
●​ Thus, on disabling the constraint by addressing through pkt1,
●​ Then pkt2 is still randomized within the constraints

​ ​ output: pkt1.addr = 136

​ ​ ​ Pkt2.addr = 5

Inline Constraints

Purpose: Whenever a function is called, the arguments passed are copied, pushed and
then popped from a stack, then operation’s return value is passed again. This sending
and sampling causes loss of performance. So, the inline functions perform its operation
at the place of calling only, hence no need to access vars from stack. This enhances
performance and is only effective when there are multiple arguments and a return value.

But, in case of inline constraints, we code it that way, literally at the line where we do
randomisation. The packet class can also have its constraints, we still can define an
inline constraint, where we randomize the packet class. In that case, both the
constraints will be considered.

--> class packet;

rand bit [3:0] addr;

rand bit [3:0] data;


constraint data_range { data > 0;

data < 10; }

endclass

module inline_constr;

initial begin

packet pkt;

pkt = new();

repeat(2) begin

pkt.randomize() with { addr == 8;};

$display("\taddr = %0d data = %0d",pkt.addr,pkt.data);

end

end

endmodule

output: addr = 8 data = 2

addr = 8 data = 5

CONCLUSION: Here, both the constraints can be followed simultaneously, so its done
without an error.

--> class packet;

rand bit [3:0] addr;

constraint addr_range {addr < 5;};

endclass
module inline_constr;

initial begin

packet pkt;

pkt = new();

repeat(2) begin

pkt.randomize() with { addr > 5;};

$display("\taddr = %0d",pkt.addr);

end

end

endmodule

output: Error-[CNST-CIF]: Constraints inconsistency failure

testbench.sv, 15

Constraints are inconsistent and cannot be solved.

Please check the inconsistent constraints being printed above and rewrite them.

addr = 0

FUNCTIONS IN CONSTRAINTS: The constraint can also equate the concerned var
with a function’s return value.

→ class packet;

rand bit [3:0] s_addr;

rand bit [3:0] e_addr;

constraint csa {s_addr == startAddr(e_addr);}

constraint cea {e_addr >= 4;};

function bit [3:0] startAddr(input bit [3:0] e_addr);


​ bit [3:0] start_addr;

​ if(e_addr < 4) begin

​ start_addr = 0;

​ End

​ start_addr = e_addr - 4;

​ return start_addr;

Endfunction

endclass

module m;

packet pkt;

initial begin

​ pkt = new();

​ repeat(48) begin

​ pkt.randomize() with {s_addr > 0;};

​ $display("start address = %d \n end address = %d", pkt.s_addr,


pkt.e_addr);

​ End

end

Endmodule

Corner case: if an intersection/common testcase can be made between the constraint


and inline constraint, then the var is randomized within both the constraint. And in a
nutshell, I made a constraint, csa for start address, which implicitly depends on
end_address, hence the constraint of end address is also responsible for the value of
start address. So, when a corner case hits, where, following the ‘cea’ constraint
randomizes end address to 4, the function returns 0, which is given to start address.
This is a violation, which is not calibrated during the compile time. And hence, the
program runs, randomizes the value and as soon as the end address is randomized to
4, the simulator gives an error there and then at runtime.
But, when I use this constraint for start_addres:

​ constraint csa {s_addr inside{[startAddr(e_addr):e_addr]};}

Then in that case, there is no error, even at runtime and whenever end address is
randomized to, 4, the ‘csa’ constraint automatically adjusts to a value between 1 to 4,
instead of 0 to 4 and doesn’t give any error.

Conclusion: If the function inside a var’s constraint uses other var’s


constrained randomisation, then during compile time, in presence of an
inline constraint, the var’s constraint and inline constraint are compared,
but not the inline function’s argument var’s constraint

Soft Constraints: Now, during such corner cases, it is important to specify what
constraint to override and continue the randomisation. To specify a constraint block that
can be overridden, we use ‘soft’ keyword.

--> class packet;

​ rand bit [3:0] addr;

​ constraint addr_range { soft addr > 6; }

endclass

module soft_constr;

initial begin

packet pkt;

pkt ​= new();

repeat(2) begin

pkt.randomize() with { addr < 6;};

$display("\taddr = %0d",pkt.addr);
End

end

endmodule

output: addr = 1

addr = 3

So, the constraint in class, which is declared as soft, is overriden by the inline
constraint.

Unique Constraint:

Unique constraint allows us to,

●​ Generate unique values across the variables


●​ Generate unique elements in an array (Fixed Size Array, Dynamic Array,
Associative array and Queue)

--> class unique_elements;

rand bit [3:0] var_1,var_2,var_3;

rand bit [7:0] array[6];

constraint varis_c {unique {var_1,var_2,var_3};}​ //all 3 vars’ value will never be


same

constraint array_c {unique {array};}​ //all elements’ value will never be same

function void display();

​ $display("var_1 = %d",var_1);

​ $display("var_2 = %d",var_2);

​ $display("var_3 = %d",var_3);
​ $display("array = %p",array);​ //%p for a complex data type like struct, array,
class etc.

endfunction

endclass

module unique_elements_randomization;

unique_elements pkt;

initial begin

​ pkt = new();

​ pkt.randomize();

​ pkt.display();

end

Endmodule

Note: A class’ handle(packet pkt) and class’ object(pkt = new()) both can be made,
either in or out of procedural blocks. ​

Bidirectional Constraints: SystemVerilog constraints are solved bidirectionally,


which means constraints on all random variables will be solved parallel.

Eg.-

​ constraint c_name { if(a == 0) b == 1;

else b == 0; }

We see that ‘b’ is dependent on ‘a’.​


but constraint solver see’s it as ‘a’ is dependent on ‘b’ and ‘b’ is dependent on ‘a’.​
i.e if ‘b’ is inline constrained as ‘1’, in order to satisfy, ‘a’ should take the value
‘0’.
As constraints are considered from all the aspects, SystemVerilog constraints are called
as bidirectional constraints.

--> class packet;

​ rand bit [3:0] a;

rand bit [3:0] b;

rand bit [3:0] c;

constraint a_value { a == b + c; }

constraint b_value { b > 6; }

constraint c_value { c < 8; }

endclass

module bidirectional_constr;

initial begin

packet pkt;

pkt = new();

repeat(5) begin

pkt.randomize();

$display("Value of a = %0d \tb = %0d \tc =%0d",pkt.a,pkt.b,pkt.c);

end

end

endmodule

Here, value of a has to be b+c and b&c also has to have their sum = a, when
randomized at once. This is why they’re bidirectional.

Solve before Constraints:


solve before constraints are used to force the constraint solver to choose the order in
which constraints are solved.

Solve before is the constraint property. solve before is used inside the constraint block
to specify the order of constraint solving. If the variables are dependent, due to the
bidirectional nature of constraints value of one variable will influence the value of
another variable.

To solve bidirectional issues, we always specify 1 flow of randomisation. For eg.-

--> class packet;

rand bit a;

rand bit [3:0] b;

constraint sab { solve a before b;}

constraint a_b { (a == 1) -> b == 0;}

endclass

module inline_constr;

initial begin

packet pkt;

pkt = new();

repeat(10) begin

pkt.randomize();

$display("\tValue of a = %0d, b = %0d",pkt.a,pkt.b);

end

end

endmodule
Now here, a will be randomized, and based on that, b will be randomised. This could
also have been opposite. That we way ‘solve b before a’ in that case, b will be
randomized first, then if b = 0, a will be given 1 and if b != 0, then a = 1.

Random System Functions:

1.​ $urandom(): randomizes 32-bit unsigned nums. We give a seed as an


argument in the function. If seed = 4. Now, it will randomize values in set of
4 i.e on continuous randomisation, values:- 5,4,3,2|5,4,3,2|5,4,3,2 so, a set
of 4 random values is created.

addr1= $urandom;​​ //addr1 is 32 bit

addr3 = addr1;​ //addr2 is 16 bit

​ addr2 = $urandom(2);

​ addr4 = {$urandom,$urandom(16)};​ //64 bit value is randomized through


concat

2.​ $random(): same for signed


3.​ $urandom_range(): with a range

Format:​ $urandom_range( int unsigned maxval, int unsigned minval = 0 )

​ addr2 = $urandom_range(20); //takes max value as '0'

12. Assertions

Assertions are primarily used to validate the behavior of a design. Warnings or errors
are generated on the failure of a specific condition or sequence of events.

Assertions are used to,

●​ Check the occurrence of a specific condition or sequence of events.


●​ Provide functional coverage.
There are 2 kinds of assertions:

​ 12.1 Immediate Assertions

Immediate assertions check for a condition at the current simulation time.

An immediate assertion is the same as an if..else statement with assertion control.

Immediate assertions have to be placed in a procedural block definition.

--> label: assert(expression) pass_statement; else


fail_statement;

Both pass and fail statements are optional

Since the assertion is a statement that something must be true, the failure of an
assertion shall have a severity associated with it. By default, the severity of an
assertion failure is an error.

Severity levels can be specified by including one of the following severity system tasks
in the fail statement:

1.​ $fatal is a run-time fatal.Its a system error and causes program to crash/end right
there.
2.​ $error is a run-time error. It still allows the program to run.
3.​ $warning is a run-time warning, which can be suppressed in a tool-specific
manner.
4.​ $info indicates that the assertion failure carries no specific severity.

If an assertion fails and no else clause is specified, the tool shall, by default calls
$error.

--> Example

//With Pass and Fail statement; Fail verbosity info;


assert(expression) $display(“expression evaluates to true”); else
$display(“expression evaluates to false”);

//Only With Pass statement;

assert(expression) $display(“expression evaluates to true”);

//With Pass and Fail statement; Fail verbosity fatal;

assert(expression) $display(“expression evaluates to true”); else


$fatal(“expression evaluates to false”);

//Only With Fail statement; Multiple statements in Faile condition and Fail
verbosity fatal;

assert(expression)

else begin

…….

…….

$fatal(“expression evaluates to false”);

End

//Only With Fail statement; Fail verbosity warning;

assert(expression) else $warning(“expression evaluates to false”);


//With Label and Fail statement; Fail verbosity warning;

label: assert(expression) else $warning(“expression evaluates to false”);

You might also like