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% Coverage Random Test Directed Test
Time
Coverage convergence
Constrained Random tests
Many runs, different seeds
Add constraints
Directed testcase
Functional Coverage Identify holes
Minimal code modifications
What should you randomize?
Device Configuration Environment Configuration Input Data Protocol exceptions Delays Errors and violations
Layered Architecture SystemVerilog
Test Environment Functional Coverage Scenario Generator
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; bit [31:0] b32; int i; byte b8; shortint s; longint l;
// // // // // //
2-state, 2-state, 2-state, 2-state, 2-state, 2-state,
single bit 32-bit unsigned integer 32-bit signed integer 8-bit signed integer 16-bit signed integer 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
bit [7:0] b_array [0:2] ;
Stored in three long words
b_array[0] b_array[1] b_array[2] Packed Array
Unused Space
Stored in one long word
bit [2:0] [7:0] b_array ;
b_array
b_array[1][6]
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 // Allocate 5 elements dyn = new[5] ; 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 // {0,1,2,3,4,5} q.insert(3,b); // 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 only when an element is written
Stores entries in a sparse matrix 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 int sum_int; 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
Adds together all the values in an array
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 : SYSTEMVERILOG $display(s.toupper()); 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 AFTER BEFORE AFTER BEFORE AFTER BEFORE AFTER BEFORE AFTER BEFORE AFTER : : : : : : : : : : : : Data DATA Data DATA Data DATA Data DATA Data DATA Data DATA = = = = = = = = = = = = 11111010 11111011 11111011 11111100 11111100 11111101 11111101 11111110 11111110 11111111 11111111 00000000 == == == == == == == == == == == == 250, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255, 0, Parity Parity Parity Parity Parity Parity Parity Parity Parity Parity Parity Parity = = = = = = = = = = = = 0 0 0 1 1 0 0 1 1 1 1 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; b = new ; // Declare a handle; initialized to null // 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; b = new ; b = new ; b = null ; // // // // create a handle Allocate a new BusTran Allocate a second one, free the first 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 // Second instance, id=1 b2 = new ; $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 ; src.stats.startT = 42; dst = new src ; dst.stats.startT = 84; src end
// Create first object // Copy src to dst // Changes stats for dst &
Complex class copy using new
src id=3 stats
startT=42
dst
id=3 stats
src
id=3 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
src
id=3 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] reset Arbiter
grant[1:0] clk
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 clk
Arbiter
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; constraint c_x_len { (x==0) -> len == 1; solve x before len; } endclass : SolveBefore
// 0 to 65536
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 = 8: len = 1: len = endcase $display(len end
$urandom_range(0,2); // 10% : 0, 1 or 2 $urandom_range(3,5); // 80% : 3, 4 or 5 $urandom_range(6,7); // 10% : 6 or 7 = %0d,len);
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; env.gen_cfg; env.build; env.run; env.wrapup; end endprogram
// // // // //
Construct environment Create random configuration Build the testbench environment Run the test Clean up after test & report
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
: 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,
class 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
event e1, e2; initial begin $display(@%0d: -> e1; @e2; $display(@%0d: end initial begin $display(@%0d: -> e2; @e1; $display(@%0d: end
@0: 1: before trigger @0: 2: before trigger @0: 1: after trigger
1: before trigger,$time);
1: after trigger,$time);
2: before trigger,$time);
2: after trigger,$time);
Waiting for an event trigger
@0: 1: @0: 2: event e1, e2; @0: 1: initial begin @0: 2: $display(@%0d: 1: before trigger,$time);
-> 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
before trigger before trigger after trigger after trigger
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); @bus.cb; bus.cb.addr <= t.addr; sem.put(1); endtask endprogram // Get the key to the bus // Drive signals onto bus // Release the key back
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 (from test)
Generator
copy
Generated stream
Factory Patterns
Blueprint (from test)
Generator
copy
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; forever begin . <pre_callback> transmit(tr); <post_callback> . end endtask task pre_callback; . endtask
task post_callback; . 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); 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 <=
req-gnt waveform Example 1
0 req
10
15
20
25
30
35
40
45
50
55
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); 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 <=
req-gnt waveform Example 2
0 req
10
15
20
25
30
35
40
45
50
55
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 @ (posedge clk); gnt @ (posedge clk); req repeat (2) @ (posedge $finish; end
clk); req <= 1'b1 ; <= 1'b1 ; <= 1'b0 ; gnt <= 1b0; clk);
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 req
10
15
20
25
30
35
40
45
50
55
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 c
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 c
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. 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) $rose(in) |=> detect);
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}; bins lo = {[1:3]}; bins hi[] = {[8:$]}; bins misc = default; } endgroup
// // // //
A 1 8 1
bin for kind==0 bin for values 1:3 separate bins bin for all the rest
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; kind port: coverpoint tr.port; cross kind, port; endgroup // Create coverpoint // Create coverpoint port // Cross kind and port
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