You are on page 1of 218

SystemVerilog

for Verification

Author: G. Syed Sheeraj


Agenda
Day 1
Introduction
Data Types
Procedural Statements and Routines
Day2
Basic OOP
Connecting the Testbench and Design
Randomization
Day3
Threads and Interprocess Communication
Advanced OOP concepts
Advanced Interfaces
Day4
Functional Coverage
Assertions
The Verification Process

What is the goal of verification?


To make sure that the design is an accurate representation of
the specification. Bugs are what you get when there is a
discrepancy.
Each bug found before tape-out is one fewer that ends up in
the customers hand.
Once you verified that the DUT performs its designated
functions correctly, you need to see how it operates when
there are errors.
Basic Testbench Functionality

The purpose of a testbench is to determine the correctness of


the design under test (DUT). This is accomplished by the
following steps.
Generate stimulus
Apply stimulus to the DUT
Capture the response
Check for correctness
Measure progress against the overall verification goals
Directed Testing vs.
Constrained Random Testing

100%

Random
Coverage

Test
Directed
Test

Time
Coverage convergence

Constrained
Random tests Many runs,
different seeds

Add
Directed
constraints
testcase Functional
Coverage

Minimal code
Identify
modifications
holes
What should you randomize?

Device Configuration
Environment Configuration
Input Data
Protocol exceptions
Delays
Errors and violations
Layered Architecture -
SystemVerilog

Test

Environment
Scenario Generator

Functional Coverage
Functional Agent Scoreboard Checker

Command Driver Assertions Monitor

Signal DUT
Data Types
Data Types

Two-state : better performance, reduced memory usage


Queues, dynamic and associative arrays and automatic
storage: reduced memory usage, built-in support for
searching and sorting
Unions and packed structures: allows multiple views of
same data
Classes and structures: support for abstract data structures
Strings: built-in string support
Enumerated types: code is easier to write and understand
Built-in Data Types

Verilog provides reg, wire, integer, time, real


logic type
Can be driven by continuous assignments, gates and modules
in addition to being a variable
Two-state types
bit Unsigned
byte, shortint, int, longint Signed
Two-state Data Types Examples

bit b; // 2-state, single bit


bit [31:0] b32; // 2-state, 32-bit unsigned integer
int i; // 2-state, 32-bit signed integer
byte b8; // 2-state, 8-bit signed integer
shortint s; // 2-state, 16-bit signed integer
longint l; // 2-state, 64-bit signed integer

// To get unsigned from signed types


byte unsigned u_byte; // 2-state, 8-bit unsigned
integer
Fixed-Size Arrays

SystemVerilog lets you use the shortcut of just giving the


array size (C-Style)
int sv_mem[16]; // equivalent to int sv_mem[0:15];
Multidimensional arrays
int array2 [0:7] [0:3]; // verbose declaration
int array3 [8] [4]; // compact declaration
Initializing an array
int ascend [4] = {0,1,2,3}; // initialize 4 elements
Basic array operations

initial
begin
bit [31:0] src[5], dst[5];
for (int i=0; i< $size(src); i++)
src[i] = i;
foreach (dst[j])
dst[j] = src[j] * 2; // dst doubles src values
end
For multidimensional arrays, use
foreach (md[i,j])
Multi-dimensional Array

Initialize and step through multi dimensional array


$display("New value:");
int md [2] [3];
md = {{9, 8, 7}, 3{5}}; // Replicate last 3 values
foreach (md[i,j]) // Yes, this is the right syntax
$display("md[%0d][%0d] = %0d", i, j, md[i][j]);

New value:
md[0][0] = 9
md[0][1] = 8
md[0][2] = 7
md[1][0] = 5
md[1][1] = 5
md[1][2] = 5
Basic array operations
copy and compare
Without using loops
initial
begin
bit [31:0] src[5] = {0,1,2,3,4},
dst[5] = {5,4,3,2,1};
// Aggregate compare the two arrays
if (src == dst)
$display(src == dst);
else
$display(src != dst);
// Aggregate copy all src values to dst
dst = src ;
end Comparison limited to
equality and inequality
Packed and Unpacked Arrays

Unpacked Array Stored in three long words


bit [7:0] b_array [0:2] ;
b_array[0]
b_array[1] Unused Space
b_array[2]
Stored in one long word
Packed Array
bit [2:0] [7:0] b_array ;
b_array[1][6]
b_array
Packed Arrays Usage Example

For some data types, we may want to access the entire


value and also divide it into smaller elements
A SystemVerilog packed array is treated as both an array
and a single value
Only Fixed arrays can be packed.

bit [3:0] [7:0] bytes; // 4 bytes packed into 32-bits


bytes = 32hdead_beef;
$displayh(bytes, // Show all 32-bits
bytes[3], // most significant byte "de"
bytes[3][7]); // most significant bit "1
Dynamic Arrays

Arrays which can grow and shrink


Array size will be decided at run-time
Declared using a empty word subscript []
Array is initially empty and space is allocated when new[]
is called
If a name of an array is passed to new[] operator, then the
values in the array are copied
Dynamic Array - Example

int dyn[], d2[]; // Empty dynamic arrays


initial begin
dyn = new[5] ; // Allocate 5 elements
foreach (dyn[j])
dyn[j] = j; // Initialize
d2 = dyn; // copy a dynamic array
dyn = new[20](dyn); // Expand and copy
dyn = new[100]; // Allocate 100 new integers
// old values are lost
dyn.delete; // Delete all elements
end
Queues

Queues can also grow and shrink similar to dynamic arrays


But with a queue you can add and remove elements
anywhere
Search and sort can be done easily
Declared using a $ word subscript i.e., [$]
Queue - Example

int j = 1, b[$] = {3,4},


q[$] = {0,2,5} ; // {0,2,5} Initial Queue
initial begin
q.insert(1,j); // {0,1,2,5} Insert 1 at 1
q.insert(3,b); // {0,1,2,3,4,5}
// Insert whole b at 3
q.delete(1); // {0,2,3,4,5}
// Delete #1 element
q.push_front(6) // {6,0,2,3,4,5}
j = q.pop_back; // {6,0,2,3,4} j = 5
end
Associative Arrays

Memory gets allocated


Stores entries in a sparse matrix only when an element
is written
Declared with wildcard syntax [*]
data
index 0 3 42 1000 4521 200,000
The memory used to store the above is far less than would
be needed to store a fixed or dynamic array with 200,000
entrires
You can use the function exists to check if an element
exists. Ex : if (data.exists(4521))
Associative Array - Example
assoc is initialized
at indices
1, 2, 4, 8, 16 etc

initial begin
logic [63:0] assoc[*], idx = 1;
// Initialize widely scattered values
repeat (64) begin
assoc[idx] = idx ;
idx = idx << 1;
end
// step through all index values with foreach
foreach (assoc[i])
$display(assoc[%h] = %h,i, assoc[i]);
Associative Array Example
(contd.)
// step through all index values with functions
if (assoc.first(idx))
begin
do
$display(assoc[%h] = %h,idx,assoc[idx]);
while (assoc.next(idx));
end
// Find and delete first element
assoc.first(idx);
assoc.delete(idx);
end
Array Methods

Array reduction methods


sum, product, and, or, xor
Array locator methods
min, max, unique
find, find_index, find_first, find_first_index,
find_last, find_last_index
sum-with
Array Reduction Method - sum

bit on[10]; // Array of single bits Adds together


int sum_int; all the values in an array
initial begin
foreach (on[i])
on[i] = i; // on[i] gets 0 or 1
// Print the single-bit sum
$display("on.sum = %0d", on.sum); // on.sum = 1
// Sum the values using 32-bits as sum_int is 32-bits
sum_int = on.sum;
$display("sum_int = %0d", sum_int); // sum_int = 5
end
Array Locator Methods

What is the largest value in an array? Does an


array contain a certain value?
Methods always return a queue
int f[6] = {1,6,2,6,8,6};
int q[$] = {1,3,5,7}, tq[$];
tq = q.min; // {1}
tq = q.max; // {7}
tq = f.unique; // {1,6,2,8}

int d[] = {9,1,8,3,4,4}, tq[$];


// Find all elements greater than 3
tq = d.find with (item > 3); // {9,8,4,4}
tq = d.find_index with (item > 3); // {0,2,4,5}
tq = d.find_first with (item >99); // {}- none found
tq= d.find_first_index with (item ==8); // {2} d[2] =8
Choosing a Storage Type
Choose the right storage type based on flexibility, memory
usage, speed and sorting.
Fixed size packets and accessed sequentially -> Fixed Size
Array
Variable size packets and accessed sequentially -> Dynamic
Array
Queues are great where the number of elements grow and shrink
and you need search and sort functionalities. Ex : Scoreboard
Content Addressable memories -> Associative Array
Modeling very large memories -> Associative Array
Command names and values from a file -> Associative Array
using the command as a string index.
User-Defined Types

Create new types using the typedef statement


parameter OPSIZE = 8;
typedef reg [OPSIZE-1:0] opreg_t;
opreg_t op_a, op_b;
// Creating a struct and a new type
struct {bit [7:0] r,g,b;} pixel;
typedef struct {bit [7:0] r,g,b;} pixel_s;
pixel_s my_pixel;
Enumerated Types

Creates a strong variable type that is limited to a set


of specified names.
Ex : instruction opcode, state machine value
enum {RED, BLUE, GREEN} color;
// Defining enumerated values
typedef enum {INIT, DECODE=2,IDLE} fsm_type_e;
color = color.first;
do
begin
$display(Color = %0d/%0s,color,color.name);
color = color.next;
end
while (color != color.first); // Done at wrap-around
Converting to and from
Enumerated Types

typedef enum {RED, BLUE, GREEN} COLOR_E;


COLOR_E color, c2;
integer c;
initial begin
c = color; // Convert from enum to integer
c++; // Increment integer
if (!$cast(color, c)) // Cast integer back to enum
$display("Cast failed for c=%0d", c);
$display("Color is %0d / %0s", color, color.name);
c2 = COLOR_E(c); // No type checking done
end
Constants and Strings

Const modifier allows you to make a variable that can be


initialized in the declaration.
initial begin
const byte colon = :;
.
end
SystemVerilog string type holds variable length strings.
Unlike C, there is no null character at the end of the string.
Strings use dynamic memory allocation.
String Methods

string s;
initial begin
s = SystemVerilog;
$display(s.getc(0)); // Display : 83
$display(s.toupper()); // Display : SYSTEMVERILOG
s = {s,3.1b}; // SystemVerilog3.1b
s.putc(s.len()-1,a); // change b -> a
$display(s.substr(2,5)); // Display : stem
my_log( $psprintf(%s %5d,s,42));
end
task my_log (string message);
$display(@%0d: %s,$time,message);
endtask
Expression Width

bit [7:0] b8;


bit one = 1b1; // Single bit
$displayb(one + one); // A: 1+1 = 0
b8 = one + one; // B: 1+1 = 2
$displayb(b8);
$displayb(one + one + 2b0); // C: 1+1 =
2 with constant
$displayb(2(one) + one); // D: 1+1 = 2
with cast
Procedural Statements
and Routines
Procedural Statements

Can declare a variable inside a for loop


Increment ++ and decrement -- operators are available
The above operators in both pre and post forms
Can put the same label on matching end and join
Can put a label on statements like endmodule, endtask,
endfunction etc
break and continue statements to control the flow in loop
structures
Procedural statements and
operators - Example
initial begin : example
integer array [10], sum, j;
// Declare i inside for statement
for (int i=0; i<10; i++)
array[i] = i;
// Add up values in the array
sum = array[9] ;
j = 8;
do // do while loop
sum += array[j]; // Accumulate
while(j--); // Test if j==0
$display(Sum=%4d,sum); // %4d specify width
end : example // End label
Tasks, Functions and Void
Functions

Multiple statements without requiring begin..end block.


Function can have output port, inout port.
In SystemVerilog, if you want to call a function and ignore its
return value, cast the result to void.
void (my_func(42));
Any debug routine should be a void function rather than a
task so that it can be called from any function or task
function void print_state ();
$display(@%0d : state = %0s,$time,cur_state.name);
endfunction
Tasks, Functions

Task and function can be defined inside following :


modules, packages, interfaces, program blocks, class
Task/function can be called inside following :
program blocks, modules, procedural blocks
Routine Arguments

C-style routine arguments


task mytask1 (output logic [31:0] x,
input logic y);

endtask
Argument direction
task T3 (a,b,output bit [15:0] u, v);
The arguments a and b are input logic, 1 bit wide. The
arguments u and v are 16-bit output bit types.
Advanced argument types

You can pass an array into a routine


You can specify that an argument is passed by
reference, rather than copying it
If you dont want the routine to change the array
values, use const ref type. With this the compiler
checks that your routine does not modify the
array.
function void print_sum (const ref int a[]);
int sum = 0;
for (int i = 0; i <a.size; i++)
sum += a[i];
$display(The sum of the array is %d,sum);
endfunction
Default argument values

In SystemVerilog you can specify a default value


that is used if you leave out an argument in the
call
function void print_sum (ref int a[],
input int start = 0,
input int last = -1);
int sum = 0;
if (last == -1 || last > a.size)
last = a.size ;
for (int i = start; i <last; i++)
sum += a[i];
$display(The sum of the array is %d,sum);
endfunction
Pass by reference - Example

module function_by_ref ();


reg [7:0] data ;
reg parity_out;
reg [7:0] odata;

function automatic reg parity (ref reg [7:0] idata);


parity = 0;
for (int i= 0; i < 8; i ++) begin
parity = parity ^ idata[i];
end
// We can modify the data passed through reference
idata = idata + 1 ;
endfunction : parity

initial
begin
parity_out = 0;
data = 0;
Pass by reference
Example cntd.
for (int i=250; i<256; i++) begin
#5;
data = i;
$display(" BEFORE : Data = %b == %3d, Parity = %d",
data, data, parity_out);
parity_out = parity (data);
$display (" AFTER : DATA = %b == %3d, Parity = %d",
data, data, parity_out);
end
#30 $finish;
end
endmodule
Pass by reference
Example Output
Output of above Example is :

BEFORE : Data = 11111010 == 250, Parity = 0


AFTER : DATA = 11111011 == 251, Parity = 0
BEFORE : Data = 11111011 == 251, Parity = 0
AFTER : DATA = 11111100 == 252, Parity = 1
BEFORE : Data = 11111100 == 252, Parity = 1
AFTER : DATA = 11111101 == 253, Parity = 0
BEFORE : Data = 11111101 == 253, Parity = 0
AFTER : DATA = 11111110 == 254, Parity = 1
BEFORE : Data = 11111110 == 254, Parity = 1
AFTER : DATA = 11111111 == 255, Parity = 1
BEFORE : Data = 11111111 == 255, Parity = 1
AFTER : DATA = 00000000 == 0, Parity = 0
Local Data Storage

program automatic test ;


task wait_for_mem (input [31:0] addr, expect_data,
output success);
while(bus.addr !== addr)
@(bus.addr);
success = (bus.data == expect_data);
endtask
endprogram
You can call this task multiple times concurrently as the
addr and expect_data arguments are stored separately for
each call
Time Values

`timescale compiler directive You must compile the files


in proper order to be sure all the delays use the proper
scale and precision.
The timeunit and timeprecision declarations eliminate
this ambiguity.
Put these in every module that has delay.
module timing;
timeunit 1ns;
timeprecision 1ps;

endmodule
Day 1 Summary
logic, bit, byte, shortint, int, longint

Fixed size arrays, dynamic arrays, queues,


associative arrays
Packed arrays and unpacked arrays
User defined types, structures, enumerated types,
constants and strings
Improvements in procedural statements and
operators
Arguments : C-style declarations, sticky direction,
default values, pass by reference
Automatic for calling multiple times
Time values
ThankYou
Day 2 - Agenda

Basic OOP
Connecting the Testbench and Design
Randomization
Basic OOP
Introduction

OOP Object Oriented Programming


Lets you create complex data types and tie them together
with routines that work with them
When you work with transactions instead of signal
transitions, you are more productive
Your first Class
class BusTran ;
bit [31:0] addr, crc, data[8];
function void display;
$display(BusTran: %h,addr);
endfunction : display
function void calc_crc;
crc = addr ^ data.xor;
endfunction : calc_crc
endclass : BusTran
OOP Terminology

Class Basic building block containing routines and


variables
Object An instance of a class
Handle A pointer to an object
Property A variable that holds a data inside class
declaration
Method The procedural code that manipulates variables
Prototype The header of a routine that shows the name,
type and argument list
Creating new Objects

BusTran b; // Declare a handle; initialized to null


b = new ; // Allocate a BusTran object, returns the
// address where the object is stored

User defined new function


class BusTran;
logic [31:0] addr, crc, data[8];
function new (logic [31:0] addr=3,d=5);
this.addr = addr;
foreach(data[i]) data[i] = d;
endfunction
endclass : BusTran
Object Deallocation

BusTran b; // create a handle


b = new ; // Allocate a new BusTran
b = new ; // Allocate a second one, free the first
b = null ; // Deallocate the second
SystemVerilog performs automatic garbage collection
when no more handles refer to an object
Static Variables vs Global
Variables

class BusTran;
static int count = 0; // Number of objects created
int id ; // unique instance id
function new ;
id = count++;
endfunction
endclass : BusTran
BusTran b1, b2;
initial begin
b1 = new ; // First instance, id=0
b2 = new ; // Second instance, id=1
$display(Second id=%d,count=%d,b2.id,b2.count);
$display(First id=%d,count=%d,b1.id,b1.count);
end // Display : Second id=1,count=2
// Display : First id=0,count=2
Class Routines

class BusTran;
bit [31:0] addr, crc, data[8];
extern function void display();
endclass : BusTran
function void BusTran::display();
$display(@%0d: BusTran addr=%h, crc=%h, addr,crc);
$write(\tdata[0-7]=);
foreach (data[i]) $write(%d ,data[i]);
$display();
endfunction : display
Scoping Rules

A scope is a block of code such as a module, program,


task, function, class, or begin-end block.
The for and foreach loops automatically create a block so
that an index variable can be declared or created local to
the scope of the loop.
If you forget to declare a variable, SystemVerilog looks up
the higher scopes until it finds a match.
Suggestion : Declare all your variables in the smallest
scope that encloses all uses of the variable.
Name scope
int limit; // $root.limit
program p;
int limit, i; // $root.p.limit
class Foo;
int limit, array[]; // $root.p.Foo.limit
task print (int limit); // $root.p.Foo.print.limit
for (int i=0; i<limit;i++)
$display();
endtask : print
endclass : Foo
initial begin
int limit = $root.limit; // $root.p.$unnamed.limit
end
endprogram : p
Using one class inside another

A class can contain an instance of another class, using a


handle to an object.

class BusTran;
bit [31:0] addr, crc, data[8];
statistics stats;
endclass : BusTran class Statistics;
time startT, stopT;
static int ntrans = 0;
static time total_elapsed_time;
endclass : Statistics
Understanding Dynamic Objects

Passing objects to routines


task generator;
BusTran b;
b = new ;
transmit(b);
endtask
task transmit(BusTran b);

endtask : transmit
Understanding Dynamic Objects

Modifying handle in a task


task create_packet (BusTran bt);
bt = new ;
bt.addr = 42;
endtask
BusTran b;
initial begin
create_packet(b); // Call bad routine
$display(b.addr); // Fails because b=null
end
task create_packet (ref BusTran bt); // Good

endtask : create_packet
Understanding Dynamic Objects

Modifying objects in flight


task generator_bad (int n);
BusTran b ;
b = new ;
repeat(n) begin
b.addr = $random();
$display(Sending addr = %h,b.addr);
transmit(b);
end
endtask : generator_bad
Understanding Dynamic Objects

Modifying objects in flight


task generator_good (int n);
BusTran b ;
repeat(n) begin
b = new ;
b.addr = $random();
$display(Sending addr = %h,b.addr);
transmit(b);
end
endtask : generator_good
Copying Objects

Copying an object with new


BusTran src, dst;
initial begin
src = new ;
dst = new src ;
end

The above is a shallow copy, similar to photocopy


of the original.
If the class contains handle to another class, only
the top level object is copied by new, not the
lower level one.
Copying Objects

Copying a complex class with new


class BusTran ;
bit [31:0] addr, crc, data[8];
static int count = 0;
int id ;
Statistics stats;
function new ;
stats = new ;
id = count++ ;
endfunction
endclass
Copying Objects

Copying a complex class with new


BusTran src, dst ;
initial begin
src = new ; // Create first object
src.stats.startT = 42;
dst = new src ; // Copy src to dst
dst.stats.startT = 84; // Changes stats for dst &
src
end
Complex class copy using new

id=3
src
stats startT=42

dst id=3
stats

id=3
src
stats startT=84

dst id=3
stats
Copying Objects

Complex class with deep copy function


class BusTran ;

function BusTran copy;
copy = new ; // construct destination
copy.addr = addr ; // Fill in data values
copy.crc = crc ;
copy.data = data ;
copy.stats = stats.copy ; // Call copy for stats
id = count++;
endfunction : copy
endclass
Complex class copy using
copy function

id=3
src
stats startT=42

dst id=4
stats startT=84
Connecting the
Testbench and Design
Separating the Testbench and
Design

The testbench forms the real world around the design,


mimicking the entire environment

Testbench

request[1:0] grant[1:0]
Arbiter clk
reset
Communication with ports

module arb_port (output logic [1:0] grant,


input logic [1:0] request,
input logic reset,
input logic clk);

always @ ( posedge clk or posedge reset) begin
if (reset)
grant <= 2b00;
else

end
endmodule : arb_port
Communication with ports

module test (input logic [1:0] grant,


output logic [1:0] request,
output logic reset,
input logic clk);
initial begin
@(posedge clk) request <= 2b01;
$display(@%0d : Drove req=01,$time);
repeat(2) @ (posedge clk);
if (grant != 2b01)
$display(@%0d: a1: grant != 2b01,$time);

$finish;
end
endmodule : test
Top module without an interface

module top;
logic [1:0] request, grant;
logic reset;
bit clk;
always #5 clk = ~clk;
arb_port a1 (grant, request, reset, clk);
test t1 (grant, request, reset, clk);
endmodule : top
The Interface Construct

interface arb_if (input bit clk);


logic [1:0] grant, request;
logic reset;
endinterface : arb_if

Testbench Interface Arbiter


clk
Top module using simple
arbiter interface
module top;
bit clk;
always #5 clk = ~clk;

arb_if arbif(clk);
arb a1 (arbif);
test t1 (arbif);
endmodule : top
Testbench using simple
arbiter interface
module test (arb_if arbif);
initial begin
@(posedge arbif.clk)
arbif.request <= 2b01;
$display(@%0d : Drove req=01,$time);
repeat(2) @ (posedge arbif.clk);
if (arbif.grant != 2b01)
$display(@%0d: a1: grant != 2b01,$time);

$finish;
end
endmodule : test
Arbiter using interface

module arb (arb_if arbif);



always @ ( posedge arbif.clk or posedge arbif.reset)
begin
if (arbif.reset)
arbif.grant <= 2b00;
else

end
endmodule : arb
Using modports

The modport construct in an interface lets you


group signals and specify directions.
interface arb_if (input bit clk);
logic [1:0] grant, request;
logic reset;
modport TEST (output request, reset,
input grant, clk);
modport DUT (input request, reset, clk,
output grant);
modport MONITOR (input request, grant, reset, clk);
endinterface : arb_if
Using interface with modports

// Arbiter model with interface using modports


module arb (arb_if.DUT arbif);

endmodule

// Testbench with interface using modports


module test (arb_if.TEST arbif);

endmodule
Clocking block

Synchronous signals can be bundled using clocking block.

interface mii_if;
clocking mtx @(posedge tx_clk);
output txd, tx_en, tx_err;
endclocking : mtx
clocking mrx @(posedge rx_clk);
input rxd, rx_dv, rx_err;
endclocking : mrx

Clocking block name can be just used for waiting to its edge as follows

@ (this.sigs.mtx);
this.sigs.mtx.txd <= nibble;

The synchronous edge can be changed easily later.


Stimulus Timing
(Driving and Sampling)
In SystemVerilog, testbench code is in a program block,
which is similar to a module in that it can contain code and
variables and be instantiated in other modules.
A program cannot have any hierarchy such as instances of
modules, interfaces or other programs. It can have objects
or instances of classes.
Program runs in the reactive region. Upon termination
implicitly calls $exit
Always blocks are not allowed inside program block
Program Module Interactions

The program block can read and write all signals in


modules.
But a module has no visibility into a program.
A program can call a routine in a module to perform
various actions. The routine can set values on internal
signals, also known as backdoor load.
For forcing a signal from a program block, you need to
write a task in the module to do the force and then call
from the program
Randomization
Randomization in SV

class Packet;
// The random variables
rand bit [31:0] src, dst, data[8];
randc bit [7:0] kind;
// Limit the values for src
constraint c {src >10;
src <15;}
endclass : Packet
Packet p;
initial begin
p = new;
assert(p.randomize());
transmit(p);
end
Constraint Details

// Set membership and inside operator


rand int c;
int lo, hi;
constraint c_range {
c inside {[lo:hi]}; // lo <= c and c <= hi
}
// Inverted random set constraint
constraint c_range {
!(c inside {[lo:hi]});
}
Weighted Distributions

// Weighted random distribution with dist


rand int src, dst;
constraint c_dist {
src dist {0:=40, [1:3]:=60};
// src = 0, weight = 40/220
// src = 1, weight = 60/220
// src = 2, weight = 60/220
// src = 3, weight = 60/220
}
Conditional constraints

// Constraint block with implication operator


constraint c_io {
(io_space_mode) -> addr[31] == 1b1;
}
// Constraint block with if-else operator
constraint c_len_rw {
if (op == READ)
len inside {[BYTE:LWRD]};
else
len == LWRD;
}
Choose the right arithmetic
operator

// Expensive constraint with mod and unsized variable


rand int [31:0] addr;
constraint slow_near_page_boundary {
addr % 4096 inside {[0:20], [4075:4095]};
}
// Efficient constraint with bit extract
rand bit [31:0] addr;
constraint near_page_boundary {
addr[11:0] inside {[0:20],[4075:4095]};
}
Guiding distribution with
solvebefore
class SolveBefore;
rand bit x; // 0 or 1
rand shortint unsigned len; // 0 to 65536
constraint c_x_len {
(x==0) -> len == 1;
solve x before len;
}
endclass : SolveBefore
Controlling Multiple Constraints

class Packet;
rand int length ;
constraint c_short {length inside {[1:32]}; }
constraint c_long {length inside {[1000:1023]}; }
endclass : Packet
Packet p;
initial begin
p = new ;
p.c_short.constraint_mode(0);
assert (p.randomize());
end
In-line Constraints

class Transaction;
rand bit [31:0] addr, data;
constraint c1 { addr inside {[0:100],[1000:2000]};}
endclass
Transaction t ;
initial begin
t = new;
// addr is 50-100, 1000-1500, data < 10
assert(t.randomize() with {addr >= 50;addr <=1500;
data <10;});
driveBus(t);
end
pre_randomize and
post_randomize

Sometimes you need to perform an action immediately


before every randomize call or immediately afterwards.
Ex : set nonrandom class variables (such as limits) before
randomization starts or calculate crc for random data
payload
pre_randomize and post_randomize helps for the above
situations. They are automatically called special void
functions.
rand_mode disables
randomization of variables
class Packet;
rand bit [7:0] length;
rand bit [7:0] payload[];
constraint c_valid {length > 0;
payload.size == length;}
endclass
Packet p;
initial begin
p = new;
// Make length nonrandom then randomize packet
p.length.rand_mode(0);
p.length = 42;
assert(p.randomize());
end
Iterative and Array Constraints

Array Size Constraining dynamic array size


class dyn_size;
rand reg [31:0] d[];
constraint d_size {d.size inside {[1:10]};}
endclass

Sum of elements
parameter MAX_TRANSFER_LEN=10;
class StrobePat;
rand bit strobe[MAX_TRANSFER_LEN];
constraint c_set_four {strobe.sum == 3h4;}
endclass
Atomic Stimulus Generation vs
Scenario Generation
// Command generator using randsequence
initial begin
for (int i=0;i<=15;i++) begin
randsequence (stream)
stream : cfg_read := 1 |
io_read := 2 |
mem_read := 5;
cfg_read : ;
io_read : ;
mem_read : ;
endsequence
end // for
end
Random Control

Use randcase to make weighted choice between


several actions, without having to create a class
and instance.
initial begin
int len;
randcase
1: len = $urandom_range(0,2); // 10% : 0, 1 or 2
8: len = $urandom_range(3,5); // 80% : 3, 4 or 5
1: len = $urandom_range(6,7); // 10% : 6 or 7
endcase
$display(len = %0d,len);
end
Useful distribution functions

$dist_exponential Exponential decay


$dist_normal Bell shaped distribution
$dist_poisson Bell shaped
distribution
$dist_uniform Flat distribution
$random Flat distribution, returning
signed 32-bit random
$urandom Flat distribution, returning
unsigned 32-bit random
$urandom_range Flat distribution over
a range
Random Device Configuration
Example : 4 port Ethernet Switch
class eth_cfg;
rand bit [ 3:0] in_use; // Ports used in test
rand bit [47:0] mac_addr[4]; // MAC addresses
rand bit [ 3:0] is_100; // 100Mbps mode
rand int run_for_n_frames; // #frames in test
// Force some addr bit when running in unicast mode
constraint local_unicast {
foreach (mac_addr[i]) mac_addr[i][41:40] == 2b00;
}
constraint reasonable {
run_for_n_frames inside {[1:100]};
}
endclass : eth_cfg
Random Device Configuration
Environment with eth_cfg
class Environment;
eth_cfg cfg;
eth_src gen[4];
eth_mii drv[4];
function new;
cfg = new; // Construct the cfg
endfunction
function void gen_cfg;
assert(cfg.randomize()); // Randomize the cfg
endfunction
Random Device Configuration
Environment with eth_cfg (contd.)
// Use random configuration to build the environment
function void build;
foreach (src[i])
if (cfg.in_use[i]) begin
gen[i] = new();
drv[i] = new();
if(cfg.is_100[i]) drv[i].set_speed(100);
end
endfunction
task run;
// Start the testbench structure
endtask
endclass : Environment
Random Device Configuration
Simple test with eth_cfg (contd.)
program test;
Environment env;
initial begin
env = new; // Construct environment
env.gen_cfg; // Create random configuration
env.build; // Build the testbench environment
env.run; // Run the test
env.wrapup; // Clean up after test & report
end
endprogram
Random Device Configuration
Simple test that overrides random cfg
program test;
Environment env;
initial begin
env = new; // Construct environment
env.gen_cfg; // Create random configuration
// Override random in_use turn all 4 ports on
env.cfg.in_use = 4b1111;
env.build; // Build the testbench environment
env.run; // Run the test
env.wrapup; // Clean up after test & report
end
endprogram
Day 2 - Summary
class : syntax, terminology, creation, deallocation
Class Routines : Scoping rules, one inside another,
understanding of dynamic objects, copying objects
interface : syntax, modport

program block : Timing, interaction with module

rand, randc for randomization

Constraints, weighted distribution, conditional


constraints, randomize with, solve before,
constraint_mode, rand_mode

pre_randomize, post_randomize, randsequence, randcase

Random device configuration


ThankYou
Day 3 - Agenda

Threads and Interprocess Communication


Advanced OOP concepts
Advanced Interfaces
Threads and
Interprocess
Communication
Working with Threads

Verilog has initial, always, beginend, forkjoin and


forever constructs for thread creation.
SystemVerilog adds two new ways to create threads with
the fork join_none and fork join_any statements.
In testbench to communicate, synchronize and control the
threads, verilog has constructs like event, @ event control,
the wait and disable statements.
SystemVerilog adds mailbox and semaphore for the above
need.
fork . join blocks

fork fork fork

join join_any join_none


Events

SystemVerilog enhances the verilog event in several ways.


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 that lets you
check whether an event has been triggered.
Blocking on an event in verilog

@0: 1: before trigger


event e1, e2; @0: 2: before trigger
initial begin
@0: 1: after trigger
$display(@%0d: 1: before trigger,$time);
-> e1;
@e2;
$display(@%0d: 1: after trigger,$time);
end
initial begin
$display(@%0d: 2: before trigger,$time);
-> e2;
@e1;
$display(@%0d: 2: after trigger,$time);
end
Waiting for an event trigger

@0: 1: before trigger


event e1, e2; @0: 2: before trigger
initial begin @0: 1: after trigger
@0: 2:
$display(@%0d: 1: before trigger,$time);
after trigger
-> e1;
wait(e2.triggered);
$display(@%0d: 1: after trigger,$time);
end
initial begin
$display(@%0d: 2: before trigger,$time);
-> e2;
wait(e1.triggered);
$display(@%0d: 2: after trigger,$time);
end
Passing events

class Generator;
event done;
function new (event done); // Pass event from TB
this.done = done;
endfunction
task run;
fork
begin

-> done; // Tell that test is done
end
join_none
endtask
endclass : Generator
Passing events (cntd.)

program automatic test;


event gen_done;
Generator gen;
initial begin
gen = new(gen_done); //Instantiate the Generator
gen.run; // Run the transactor
wait(gen_done.triggered); // wait for finish
end
endprogram : test
Semaphores

A Semaphore allows you to control access to a resource


Ex : A library book, A common car for family members
Semaphore can be used in a testbench when you have a
resource, such as bus, that may have multiple requestors
from inside the testbench but as part of physical design,
can only have one driver.
In SystemVerilog, a thread that requests a key when one is
not available always blocks. Multiple blocking threads are
queued in FIFO order.
Semaphore operations

Create a semaphore with one or more keys using new


method
Get one or more keys with get method
Return one or more keys with put method
If you want to try to get a semaphore, but not block, use
try_get function. It returns 1 if there are enough keys, and
0 if there are insufficient keys.
Semaphore operations

program automatic test;


semaphore sem; // create semaphore
initial begin
sem = new(1); // Allocate with 1 key
fork
sequencer; // Spawn two threads that both
sequencer; // do bus transactions
join
end
task automatic sequencer;
repeat($urandom%10) @bus.cb; // Random wait, 0-9
cycles
sendTrans; // Execute the
transaction
endtask
Semaphore operations

task automatic sendTrans;


sem.get(1); // Get the key to the bus
@bus.cb;
bus.cb.addr <= t.addr; // Drive signals onto bus

sem.put(1); // Release the key back
endtask
endprogram
Mailboxes

How do you pass information between two threads?


Generator needs to create many transactions and pass them
to a driver.
A mailbox is just like a FIFO with a source and sink.
Mailboxes can have a maximum size or can be unlimited.
When source puts a value into a sized mailbox that is full,
it blocks until data is removed.
If sink tries to remove data from a mailbox that is empty, it
blocks until data is available.
Mailboxes (cntd.)

A mailbox is an object and thus has to be instantiated by calling the


new function.
Put data into a mailbox using put method. put blocks if mailbox is full.
Remove data from a mailbox using get. get blocks if mailbox is empty.
peek task gets a copy of the data in the mailbox but does not remove it.
If you dont want your code to block, use the try_get and try_peek
functions.

generator

mailbox

driver
Mailbox in a testbench
program mailbox_example;
class Generator;
Transaction tr;
mailbox mbx;
function new(mailbox mbx);
this.mbx = mbx;
endfunction
task run;
repeat(10) begin
tr = new;
assert(tr.randomize());
mbx.put(tr); // Send out transaction
end
endtask : run
endclass : Generator
Mailbox in a testbench (cntd.)
class Driver;
Transaction tr;
mailbox mbx;
function new(mailbox mbx);
this.mbx = mbx;
endfunction
task run;
repeat(10) begin
mbx.get(tr); // Fetch next transaction
@(posedge busif.cb.ack);
busif.cb.kind <= tr.kind;
end
endtask : run
endclass : Driver
Mailbox in a testbench (cntd.)
mailbox mbx; // Mailbox connecting gen & drv
Generator gen;
Driver drv;
initial begin
mbx = new;
gen = new(mbx);
drv = new(mbx);
fork
gen.run();
drv.run();
join
end
endprogram : mailbox_example
Advanced OOP
Inheritance

Inheritance allows a new class to be derived from an


existing one in order to share its variables and routines.
The original class is known as the base or super class.
The new one, since it extends the capability of the base
class, is called the extended class.
Inheritance provides reusability by adding features, such as
error injection, to an existing class, the base transaction,
without modifying the base class.
Base Transaction Class

class Transaction;
rand bit [31:0] src, dst, data[8]; // Variables
bit [31:0] crc;
virtual function void calc_crc;
crc = src ^ dst ^ data.xor;
endfunction
virtual function void display;
$display(Tr: src=%h, dst=%h, crc=%h,
src,dst,crc);
endfunction
endclass
Extended Transaction class

class BadTr extends Transaction;


rand bit bad_crc;
virtual function void calc_crc;
super.calc_crc(); // Compute good crc
if(bad_crc) crc = ~crc; // Corrupt crc bits
endfunction
virtual function void display;
$write(BadTr: bad_crc=%b,bad_crc);
super.display();
endfunction
endclass : BadTr
Factory Patterns

Blueprint
Generator copy
(from test)

Generated
stream
Factory Patterns

Blueprint
Generator copy
(from test)

Generated
stream
Generator class using
Factory Patterns
class Generator;
mailbox gen2drv;
Transaction blueprint;
function new(mailbox gen2drv);
this.gen2drv = gen2drv;
endfunction
function build;
blueprint = new;
endfunction
Generator class using
Factory Patterns (cntd.)

task run;
Transaction tr;
forever begin
assert(blueprint.randomize);
tr = blueprint.copy;
gen2drv.put(tr);
end
endtask
endclass : Generator
Using the extended Transaction
class
program automatic test;
Environment env;
initial begin
env = new;
env.build; // Construct the blueprint
BadTr bad;
bad = new; // Create a bad transaction
env.gen.blueprint = bad; // Replace the blueprint
// with bad one
env.run; // Run the test
env.wrap_up; // Cleanup afterwards
end
endprogram
Callbacks
To create a verification environment that you can use for all
tests with no changes.
The key requirement is that the testbench must provide a hook
where the test program can inject new code without modifying
the original classes.
Driver may want to do: inject errors, drop the transaction, delay
the transaction, synchronize this transaction with others, put the
transaction in scoreboard, gather functional coverage data.
Driver just needs to call back a routine defined in the top-level
test.
The beauty of this technique is that the callback routine can be
defined differently in every test.
The test can add new functionality to the driver using callbacks
without editing Driver class.
Callback flow

task Driver::run; task pre_callback;


forever begin .
. endtask
<pre_callback>
transmit(tr);
<post_callback>
. task post_callback;
end .
endtask endtask
Creating a Callback
Base callback class
class Driver_cbs; // Driver callbacks
virtual task pre_tx(Transaction tr, ref bit drop);
// By default, callback does nothing
endtask
virtual task post_tx(Transaction tr);
// By default, callback does nothing
endtask
endclass
Driver class with callbacks

class Driver;
Driver_cbs cbs[$]; // Queue of callbacks
task run;
bit drop;
Transaction tr;
forever begin
agt2drv.get(tr);
foreach(cbs[i]) cbs[i].pre_tx(tr,drop);
if(!drop) transmit(tr);
foreach(cbs[i]) cbs[i].post_tx(tr);
end
endtask
endclass
Using callback for randomly
dropping a packet
class Driver_cbs_drop extends Driver_cbs;
virtual task pre_tx(Transaction tr, ref bit drop);
// Randomly drop 1 out of every 100 transactions
drop = ( $urandom_range(0,99) == 0);
endtask
endclass : Driver_cbs_drop
program automatic test;

Driver_cbs_drop dcd;
dcd = new;
env.drv.cbs.push_back(dcd);
env.run;

endprogram
Advanced Interfaces
Virtual Interfaces

In a network switch, a single Driver class may connect to


many interfaces, one for each input channel of the DUT.
Write a generic Driver, instantiate it N times and have it
connect each of the N physical interfaces.
You can do the above in SystemVerilog by using a virtual
interface that is merely a handle to a physical interface.
Interface with clocking block

// Rx interface with modports and clocking block


interface Rx_if (input logic rclk);
logic [7:0] data;
logic soc, en, clav;
clocking cb @(posedge rclk);
output data, soc, clav;
input en;
endclocking : cb
modport TB (clocking cb);
endinterface : Rx_if
Interface with clocking block
(cntd.)
// Tx interface with modports and clocking block
interface Tx_if (input logic tclk);
logic [7:0] data;
logic soc, en, clav;
clocking cb @(posedge tclk);
input data, soc, en;
output clav;
endclocking : cb
modport TB (clocking cb);
endinterface : Tx_if
Testbench using physical
interfaces
program automatic test(Rx_if.TB Rx0, Rx1, Rx2, Rx3,
Tx_if.TB Tx0, Tx1, Tx2, Tx3,
input logic clk, output logic rst);
bit [7:0] bytes[`ATM_SIZE];
initial begin
rst <= 1;
Rx0.cb.data <= 0;

receive_cell0;
end

Testbench using virtual interfaces

program automatic test(Rx_if.TB Rx0, Rx1, Rx2, Rx3,


Tx_if.TB Tx0, Tx1, Tx2, Tx3,
input logic clk, output logic rst);
Driver drv[4]; Monitor mon[4]; Scoreboard scb[4];
virtual Rx_if.TB vRx[4] = `{Rx0, Rx1, Rx2, Rx3};
virtual Tx_if.TB vTx[4] = `{Tx0, Tx1, Tx2, Tx3};
initial begin
foreach(scb[i]) begin
scb[i] = new(i);
drv[i] = new(scb[i].exp_mbx, i, vRx[i]);
mon[i] = new(scb[i].rcv_mbx, i, vTx[i]);
end
end
endprogram
Driver class using virtual
interfaces
class Driver;
int stream_id;
bit done = 0;
mailbox exp_mbx;
virtual Rx_if.TB Rx;
function new(mailbox exp_mbx, int stream_id,
virtual Rx_if.TB Rx);
this.exp_mbx = exp_mbx;
this.stream_id = stream_id;
this.Rx = Rx;
endfunction

endclass : Driver
Multiple Design Configurations

A mesh design example


A simple replicated component, an 8-bit counter.
This resembles a DUT that has a device such as network
chip or processor that is instantiated repeatedly in a mesh
configuration.
The key idea is that the top-level bench creates an array of
interfaces and counters.
Now the testbench can connect its array of virtual
interfaces to the physical ones.
Interface for 8-bit counter

interface cntr_if (input logic clk);


logic [7:0] din, dout;
logic reset_l. load;
clocking cb @(posedge clk);
output din, load;
input dout;
endclocking
always @cb
$strobe(@%0d:%m: out=%0d, in=%0d, ld=%0d, r=%0d, $time,
dout, din, load, reset_l);
modport DUT (input clk, din, reset_l, load, output dout);
modport TB (clocking cb, output reset_l);
endinterface : cntr_if
Counter model using cntr_if
interface

// Simple 8-bit counter with load and active low reset


module DUT (cntr_if.DUT cif);
logic [7:0] count;
assign cif.dout = count;
always @(posedge cif.clk or negedge cif.reset_l);
begin
if (cif.reset_l) count = 0;
else if (cif.load) count = cif.din;
else count++;
end
endmodule : DUT
Test top using an array of
virtual interfaces
parameter NUM_CI = 2; // Number of design interfaces
module top;
bit clk; initial forever #20 clk=~clk;
// Instantiate N interfaces
cntr_if ci [NUM_CI] (clk);
// Instantiate the testbench
test tb();
// Generate N DUT instances
generate
for (genvar i=0; i<NUM_CI; i++)
begin : dut
DUT d (ci[i]);
end
endgenerate
Testbench using an array of
virtual interfaces
program automatic test;
virtual cntr_if.TB vci[NUM_CI]; // virtual ifc array
Driver driver[];
initial begin // connect local virtual interfaces to top &
// create N drivers
vci = top.ci;
driver = new[NUM_CI];
foreach (driver[i]) begin
driver[i] = new(vci[i],i);
driver[i].reset;
end
foreach (driver[i]) driver[i].load;
end
endprogram : test
Driver class using virtual interface

class Driver;
virtual cntr_if ci;
int id;
function new(virtual cntr_if.TB ci, int id);
this.ci = ci;
this.id = id;
endfunction
task load;
fork begin
##1 ci.cb.load <= 1;

end join_none
endtask
endclass : Driver
Day 3 - Summary
fork join, fork_join_none, fork join_any
Events, semaphores and mailboxes
Inheritance, factory pattern generation and callbacks
Interfaces to virtual interfaces
ThankYou
Day 4 - Agenda

Assertions
Functional Coverage
Assertions
Assertions

An assertion specifies a behavior of the system.


Assertions are primarily used to validate the behavior of a
design.
In addition, assertions can be used to provide functional
coverage.
Two types of Assertions: Immediate assertions and
concurrent assertions.
Immediate assertions : Follow simulation event semantics
and executed like a statement in a procedural block.
Concurrent assertions : Follow clock semantics and use
sampled values of variables. (Also called as temporal
assertions)
Immediate Assertions

Is a test of an expression performed when the statement is


executed in a procedural block. (like an if statement)
If expression evaluates to 0, X or Z then it is interpreted as
false and assertion is said to fail.
Else the expression is interpreted as true and assertion is
said to pass.
assert_foo : assert(foo) $display(%m passed); else
$display(%m failed);
Immediate Assertion - Example

time t;
always @(posedge clk)
if (state == REQ)
assert (req1 || req2)
else begin
t = $time;
#5 $error("assert failed at time %0t",t);
end
Similar to $error, $fatal, $warning and $info are available.
Concurrent Assertions

Concurrent assertions describe behavior that spans over


time.
Concurrent assertion is only evaluated at clock tick.
The values of the variable used in the expression are
sampled values.
It is important to ensure that the defined clock behavior is
glitch-free. Otherwise wrong values can be sampled.
base_rule1 : assert property (cont_prop(rst,in1,in2))
pass_stat else fail_stat;
Sequences
Delay ##1 indicates that the beginning of the sequence that
follows is one clock tick later than the current clock tick.
The sequence:
req ##1 gnt ##1 !req
specifies that req be true on the current clock tick, gnt
shall be true on the first subsequent tick, and req shall be
false on the next clock tick after that.
req ##2 gnt
This specifies that req shall be true on the current clock tick,
and gnt shall be true on the second subsequent clock tick.
Sequences (cntd.)

req ##[4:32] gnt


In the above case, signal req must be true at the current
clock tick, and signal gnt must be true at some clock tick
between the 4th and the 32nd clock tick after the current
clock tick.
The time window can extend to a finite, but unbounded,
range by using $ as in the example below.
req ##[4:$] gnt
Declaration of Sequences

sequence s1; // Simple Sequence


a ##1 b ##1 c;
endsequence
sequence s20_1(data,en); // Sequence with arguments
(!frame && (data==data_bus)) ##1 (c_be[0:3] == en);
endsequence
sequence rule; // Nested sequence
@(posedge sysclk)
trans ##1 start_trans ##1 s1 ##1 end_trans;
endsequence
Declaring Properties
A property defines a behavior of the design. A property
can be used for verification as an assumption, a checker, or
a coverage specification.
In order to use the behavior for verification, an assert,
assume or cover statement must be used. A property
declaration by itself does not produce any result.
A property can be declared in
a module
an interface
a program
a clocking block
a package
a compilation-unit scope
Implication Properties
property data_end; // Overlapped Implication
@(posedge mclk)
data_phase |-> ((irdy==0) && ($fell(trdy) ||
$fell(stop))) ;
endproperty
For Non-overlapped implication, use |=>. Means after one clock tick.
property data_end; // Non-Overlapped Implication
@(posedge mclk)
data_phase |=> ((irdy==0) && ($fell(trdy) ||
$fell(stop))) ;
endproperty
Assert statement
The assert statement is used to enforce a property as a
checker. When the property for the assert statement is
evaluated to be true, the pass statements of the action block
are executed. Otherwise, the fail statements of the
action_block are executed. For example,
property abc(a,b,c);
disable iff (a==2) not @clk (b ##1 c);
endproperty
env_prop: assert property
(abc(rst,in1,in2)) pass_stat else
fail_stat;
When no action is needed, a null statement (i.e.;) is
specified. If no statement is specified for else, then
$error is used as the statement when the assertion fails.
Assertions Example 1
module test;
bit clk, req, gnt;
always #5 clk = ~clk;

initial begin
repeat (2) @ (posedge clk); req <= 1'b1 ;
@ (posedge clk); gnt <= 1'b1 ; @ (posedge clk); req <=
1'b0 ;
repeat (2) @ (posedge clk);
$finish;
end

sequence s1;
req ##1 gnt ##1 !req;
endsequence : s1
property rule_s1 ;
@ (posedge clk) s1;
endproperty
assert property (rule_s1);
endmodule
req-gnt waveform Example 1

0 5 10 15 20 25 30 35 40 45 50 55

req

gnt

$finish
Assertions Example 1 - Output

"assertions_1.sv", 18: test.unnamed$$_1: started at 5s failed


at 5s
Offending 'req'
"assertions_1.sv", 18: test.unnamed$$_1: started at 15s failed
at 15s
Offending 'req'
"assertions_1.sv", 18: test.unnamed$$_1: started at 45s failed
at 45s
Offending 'req'
$finish at simulation time 55
Assertions Example 2
module test;
bit clk, req, gnt;
always #5 clk = ~clk;

initial begin
repeat (2) @ (posedge clk); req <= 1'b1 ;
@ (posedge clk); gnt <= 1'b1 ; @ (posedge clk); req <=
1'b0 ;
repeat (2) @ (posedge clk);
$finish; Overlapping
end

sequence s1;
req ##1 gnt ##1 !req;
endsequence : s1
property rule_s1 ;
@ (posedge clk) req |-> s1;
endproperty
assert property (rule_s1);
endmodule
req-gnt waveform Example 2

0 5 10 15 20 25 30 35 40 45 50 55

req

gnt

$finish
Assertions Example 2 - Output

$finish at simulation time 55


Assertions Example 3
module test;
bit clk, req, gnt;
always #5 clk = ~clk;

initial begin
repeat (2) @ (posedge clk); req <= 1'b1 ;
@ (posedge clk); gnt <= 1'b1 ;
@ (posedge clk); req <= 1'b0 ; gnt <= 1b0;
repeat (2) @ (posedge clk);
$finish;
end Non-overlapping

sequence s1;
gnt ##1 !req;
endsequence : s1
property rule_s1 ;
@ (posedge clk) req |=> s1;
endproperty
assert property (rule_s1);
endmodule
req-gnt waveform Example 3

0 5 10 15 20 25 30 35 40 45 50 55

req

gnt

$finish
Failed at
Assertions Example 3 - Output

"assertions_3.sv", 19: test.unnamed$$_1: started at 35s failed


at 45s
Offending 'gnt'
$finish at simulation time 55
Repetition in Sequences

a ##1 b ##1 b ##1 b ##1 c


Using the consecutive repetition operator [*3], which
indicates 3 iterations, this sequential behavior is specified
more succinctly:
a ##1 b [*3] ##1 c
[*N] is the repetition indicator. This repetition is called
consecutive repetition.
b ##1 a[*0:1] ##2 c
is equivalent to
(b ##2 c) or (b ##1 a ##2 c)
Repetition in Sequences (cntd.)

(a ##2 b) [*5]

This is the same as:


(a ##2 b ##1 a ##2 b ##1 a ##2 b ##1 a ##2 b ##1 a ##2 b)

To specify a finite, but unbounded, number of


iterations, the dollar sign ($) is used.
a ##1 b [*1:$] ##1 c

matches over an interval of three or more


consecutive clock ticks if a is true on the first
clock tick, c is true on the last clock tick, and b is
true at every clock tick strictly in between the first
and the last.
Goto Repetition
Goto repetition specifies finitely many iterative
matches of the operand boolean expression, with a
delay of one or more clock ticks from one match
of the operand to the next successive match and no
match of the operand strictly in between.
The overall repetition sequence matches at the last
iterative match of the operand.
Example :
a ##1 b [->2:10] ##1 c

is equivalent to
a ##1 ((!b[*0:$] ##1 b) [*2:10]) ##1 c
Go to Repetition Example

a ##1 b[->3] ##1 c


Non-consecutive repetition
Non-consecutive repetition specifies finitely many iterative
matches of the operand boolean expression, with a delay of
one or more clock ticks from one match of the operand to
the next successive match and no match of the operand
strictly in between.
The overall repetition sequence matches at or after the last
iterative match of the operand, but before any later match
of the operand.
Example:
a ##1 b [=2:10] ##1 c
is equivalent to
a ##1 ((!b [*0:$] ##1 b) [*2:10]) ##1
!b[*0:$] ##1 c
Nonconsecutive Repetition
Example

a ##1 b[=3] ##1 c


Sampled Value functions
Function $sampled returns the sampled value of the expression with respect
to the last occurrence of the clocking event
A $rose returns true if the least significant bit of the expression changed to
1. Otherwise, it returns false.
assert property (@(posedge clk) $rose(in) |=> detect);
A $fell returns true if the least significant bit of the expression changed to
0. Otherwise, it returns false.
A $stable returns true if the value of the expression did not change.
Otherwise, it returns false.
assert property (@(posedge clk) enable == 0 |=>
$stable(data));
Example : $fell (ack, clk); $rose (req);
The clocking event if not specified will be inferred from the code where it is
used.
Past function
The past values can be accessed with the $past function.
$past( expression1 [, number_of_ticks] [,
expression2] [, clocking_event])
The following three optional arguments are provided:
expression2 is used as a gating expression for the
clocking event
number_of_ticks specifies the number of clock ticks
in the past
clocking_event specifies the clocking event for
sampling expression1
The clocking event if not specified will be inferred from
the code where it is used.
Past function (cntd.)
A $past can be used in any System Verilog
expression. An example is shown below.
always @(posedge clk)
reg1 <= a & $past(b);

always @(posedge clk)


if (enable) q <= d;
always @(posedge clk)
assert (done |=> (out ==
$past(q,2,enable)) ;
and Operation
The two operands of and are sequences.
The requirement for the match of the and operation
is that both the operands must match.
The operand sequences start at the same time.
When one of the operand sequences matches, it
waits for the other to match.
The end time of the composite sequence is the end
time of the operand sequence that completes last.
Example :
When te1 and te2 are sequences, then the
composite sequence:
te1 and te2
intersect, or Operations
Intersect : Same as and, but the lengths of two
matches of the operand sequences must be the
same.
te1 intersect te2
The operator or is used when at least one of the
two operand sequences is expected to match.
The two operands of or are sequences.
If the operands te1 and te2 are expressions,
then
te1 or te2
matches at any clock tick on which at least one of
te1 and te2 evaluates to true.
first_match Operation
The first_match operator matches only the first
of possibly multiple matches for an evaluation
attempt of its operand sequence.
This allows all subsequent matches to be
discarded from consideration.
sequence t1;
te1 ## [2:5] te2;
endsequence
sequence ts1;
first_match(te1 ## [2:5] te2);
endsequence
throughout and within
Operation
The composite sequence, exp throughout seq,
matches along a finite interval of consecutive clock ticks
provided seq matches along the interval and exp
evaluates to true at each clock tick of the interval.
The composite sequence seq1 within seq2 matches
along a finite interval of consecutive clock ticks provided
seq2 matches along the interval and seq1 matches
along some sub-interval of consecutive clock ticks.
That is, the matches of seq1 and seq2 must satisfy the
following:
The start point of the match of seq1 must be no
earlier than the start point of the match of seq2.
The end point of the match of seq1 must be no later
than the end point of the match of seq2.
Detecting endpoint of a
sequence
The end point of a sequence is reached whenever
the ending clock tick of a match of the sequence is
reached, regardless of the starting clock tick of the
match.
The reaching of the end point can be tested in any
sequence by using the method ended.
sequence e1;
@(posedge sysclk) $rose(ready) ##1 proc1 ##1 proc2 ;
endsequence
sequence rule;
@(posedge sysclk) reset ##1 inst ##1 e1.ended ##1
branch_back;
endsequence
Manipulating data in a sequence
For example, if in
a ##1 b[->1] ##1 c[*2]
it is desired to assign x = e at the match of b[->1], the
sequence can be rewritten as
a ##1 (b[->1], x = e) ##1 c[*2]
The local variable can be reassigned later in the sequence,
as in
a ##1 (b[->1], x = e) ##1 (c[*2], x = x +
1)
For every attempt, a new copy of the variable is created for
the sequence. The variable value can be tested like any
other SystemVerilog variable.
Manipulating data in a sequence
(checking)
Variables can be used in sequences and can be checked for.
sequence data_check;
int x;
a ##1 !a, x = data_in ##1 !b[*0:$] ##1
b && (data_out == x);
endsequence
Manipulating data in a sequence
(arguments)
sequence sub_seq2(lv);
a ##1 !a, lv = data_in ##1 !b[*0:$] ##1
b && (data_out == lv);
endsequence
sequence seq2;
int v1;
c ##1 sub_seq2(v1) ##1 (do1 == v1);
// v1 is now bound to lv
endsequence
Declaring Properties - complex

property data_check_p;
int x;
a ##1 !a, x = data_in |=> !b[*0:$] ##1
b && (data_out == x);
endproperty
A disable iff clause can be attached to a property_expr to
yield a property_spec
disable iff (expression_or_dist) property_expr
Assume statement

The purpose of the assume statement is to allow


properties to be considered as assumptions for formal
analysis tools.
a1:assume property @(posedge clk)
req dist {0:=40, 1:=60} ;
property proto;
@(posedge clk) req |-> req[*1:$]
##0 ack;
endproperty
Cover statement

To monitor sequences and other behavioral aspects of the


design for coverage, the same syntax is used with the
cover statement.
cover property ( sequence_expr ) statement_or_null
Functional Coverage
Introduction

Code coverage : how many lines of code have been


executed.
Functional coverage is a measure of which design features
have been exercised by the tests.
Code coverage : measures the coverage with respect to
what has been implemented
Function coverage: measures the coverage with respect to
what has to be implemented to meet the functionality.
Simple Functional Coverage -
Example
To measure functional coverage, you begin with
verification plan and write an executable version of it for
simulation.
In your SystemVerilog testbench, sample the values of
variables and expressions.
These sample locations are known as coverpoints.
Multiple coverpoints that are sampled at the same time are
placed together in a covergroup.
Simple Functional Coverage -
Example
program automatic test(busifc.TB ifc);
class Transaction ;
rand bit [31:0] data;
rand bit [ 2:0] port; // Eight port numbers
endclass : Transaction
covergroup CovPort;
coverpoint tr.port; // Measure coverage
endgroup
Transaction tr = new;
Simple Functional Coverage
Example (cntd.)
initial begin
CovPort ck = new; // Instantiate group
repeat(32) begin // Run few cycles
assert(tr.randomize()); // Create trans.
ifc.cb.port <= tr.port; // and transmit
ifc.cb.data <= tr.data; // onto interface
ck.sample(); // Gather coverage
@ifc.cb; // wait a cycle
end
end
endprogram
Coverage report for the example

Coverpoint Coverage report


CoverageGroup: CovPort
Coverpoint: tr.port
Summary
Coverage: 87.50
Goal: 100
Number of Expected auto-bins: 8
Number of User defined Bins: 0
Number of Automatically Generated Bins: 7
Number of User Defined Transitions: 0
Coverage report for the example
(cntd.)

Automatically Generated Bins


Bin # hits at least
auto[1] 7 1
auto[2] 7 1
auto[3] 1 1
auto[4] 5 1
auto[5] 4 1
auto[6] 2 1
auto[7] 6 1
Anatomy of a Cover Group
A covergroup is similar to a class. Define it once
and instantiate it one or more times.
It contains coverpoints, options, formal arguments
and an optional trigger.
A covergroup encampasses one or more data
points, all of which are sampled at the same time.
A covergroup can be defined in a class or at the
program or module level.
It can sample any visible variable such as
program/module variables, signals from an
interface, or any signal in the design.
Covergroup in a class

class Transactor;
Transaction tr;
mailbox mbx_in;
covergroup CovPort;
coverpoint tr.port;
endgroup
function new(mailbox mbx_in);
CovPort = new; // Instantiate covergroup
this.mbx_in = mbx_in;
endfunction
Covergroup in a class (cntd.)

task main;
forever begin
tr = mbx_in.get; // Get the next trans.
ifc.cb.port <= tr.port; // Send into DUT
ifc.cb.data <= tr.data;
CovPort.sample(); //Gather coverage
end
endtask : main
endclass : Transactor
Triggering a Cover Group

// Covergroup with an event trigger


event trans_ready;
covergroup CovPort @(trans_ready);
coverpoint ifc.cb.port; // Measure coverage
Endgroup
Covergroups can be triggered by calling sample method
from Callback functions.
Covergroups can also be triggered on a SystemVerilog
assertion.
Controlling the number of bins

SystemVerilog creates a number of bins to


record how many times each value has been seen.
These bins are basic unit of measurement for
functional coverage.
// Limiting the number of automatic bins created
covergroup CovPort;
coverpoint tr.port
{options.auto_bin_max = 2;} // divide into 2 bins
endgroup
// Using auto_bin_max for all cover points
covergroup CovPort;
options.auto_bin_max = 2; // Affects port & data
coverpoint tr.port;
coverpoint tr.data;
endgroup
User defined bins

class Transaction;
rand bit [2:0] hdr_len; // range: 0:7
rand bit [3:0] payload_len; // range: 0:15

endclass
Transaction tr;
covergroup CovLen;
len: coverpoint (tr.hdr_len + tr.payload_len + 5b0)
{bins len[] = {[0:22]};}
endgroup
Naming the coverpoint bins

kind is a 4-bit variable that has 16 possible values.


// Specifying the bin names
covergroup CovKind;
coverpoint tr.kind {
bins zero = {0}; // A bin for kind==0
bins lo = {[1:3]}; // 1 bin for values 1:3
bins hi[] = {[8:$]}; // 8 separate bins
bins misc = default; // 1 bin for all the rest
}
endgroup
Report showing the bin names

Bin # hits at least


===============================================
hi_8 0 1
hi_9 5 1
hi_a 3 1
hi_b 4 1
hi_c 2 1
hi_d 2 1
hi_e 9 1
hi_f 4 1
lo 16 1
misc 15 1
zero 1 1
===============================================
Conditional coverage

// Disable during reset


covergroup CovPort;
// Dont gather coverage when reset == 1
coverpoint port iff (bus_if.reset);
endgroup
// Using start and stop functions
initial begin
CovPort ck = new; // Instantiate covergroup
// Reset sequence stops collection of coverage data
#1ns bus_if.reset = 1;
ck.stop();
#100ns bus_if.reset = 0;
ck.start();
end
Transition coverage

For example you can check if port ever went from 0


to 1,2 or 3.
covergroup CovPort;
coverpoint port {
bins t1 = (0 => 1), (0 => 2), (0 => 3);
}
endgroup
(0 => 1[*3] => 2) is equivalent to (0 => 1 => 1 => 1 => 2).
Ignoring bins

Use ignore_bins to tell which values to exclude from


functional coverage calculation.
bit [2:0] low_ports_0_5; // Only uses
values 0-5
covergroup CovPort;
coverpoint low_ports_0_5 {
ignore_bins hi = {[6,7]}; //Ignore
upper 2bins
}
endgroup
Cross Coverage

Example : hburst and hsize combinations


Cross coverage measures what values were seen for two or
more coverpoints at the same time.
covergroup CovPortKind;
kind: coverpoint tr.kind; // Create coverpoint
kind
port: coverpoint tr.port; // Create coverpoint port
cross kind, port; // Cross kind and port
endgroup
Excluding cross coverage bins

covergroup CovPortKind;
port: coverpoint tr.port
{bins port[] = {[0:$]};}
kind: coverpoint tr.kind
{bins zero = {0};
bins lo = {[1:3]};
bins hi[] = {[8:$]};
bins misc = default;}
cross kind, port {
ignore_bins hi = binsof(port) intersect {7};
ignore_bins md = binsof(port) intersect {0} &&
binsof(kind) intersect {[9:10]};
ignore_bins lo = binsof(kind.lo);
}
endgroup
Day 4 - Summary
Immediate assertions, concurrent assertions
Sequences, declaration of sequences
Repetition specification (consecutive, goto and non-consecutive)
Sampled value functions ($sampled, $rose, $fell, $stable, $past)
and, intersect, or, first_match, throughout, within and ended operations
Assigning variables in sequence specification and checking
Property declaration : syntax, Implication properties
assert, assume and cover statements
Functional coverage example
coverpoint, covergroup, triggering the covergroup
Controlling the number of bins, naming the bins
Conditional coverage, transition coverage
Ignore_bins and cross coverage
ThankYou

You might also like