21EC71_Module3
21EC71_Module3
Module 3
Verification Guidelines:
The verification process, basic test bench functionality, directed testing, methodology basics,
constrained random stimulus, randomization, functional coverage, test bench components, layered
testbench.
Data Types:
Built in Data types, fixed and dynamic arrays, Queues, associative arrays, linked lists, array methods,
choosing a type, creating new types with type def, creating user defined structures, type conversion,
Enumerated types, constants and strings, Expression width.
---------------------------------------------------------------------------------------------------------------------------
The typical features of an HVL (System Verilog) that distinguish it from a Hardware Description
Language such as Verilog or VHDL are
Constrained-random stimulus generation
Functional coverage
Higher-level structures, especially Object Oriented Programming
Multi-threading and interprocess communication
Support for HDL types such as Verilog‘s 4-state values
Tight integration with event-simulator for control of the design
These features allow the HVL to generate higher level abstraction test bench than HDL or C.
Verification Process
The verification process in System Verilog is an essential part of hardware design to ensure that a
digital circuit behaves as expected. System Verilog, being a hardware description and verification
language, provides various constructs and methodologies for efficient and thorough verification. The
verification process generally involves the following stages:
The testbench is an essential part of the verification process. It includes all the components needed to
simulate and verify the design (DUT - Device Under Test). A typical testbench consists of:
Before starting any verification activities, a verification plan needs to be created. This test plan
identifies:
The test plan provides a roadmap for verification and helps ensure all aspects of the design are
covered.
3. Generate Stimulus
Stimulus generation is the process of creating input signals to the DUT. There are several approaches
to this:
Random Stimulus Generation: System Verilog's random capabilities allow for random
input generation. This is typically used in constrained random verification to explore a wide
range of input combinations.
Directed Tests: These are test cases that are specifically written to target particular behaviors
or functions of the DUT.
Constrained Random Testing: A combination of random stimulus generation with
constraints to ensure legal or meaningful inputs are generated while still exploring a wide
variety of scenarios.
SystemVerilog's randc (random cyclic) and constraint mechanisms are key tools in this process.
The testbench and DUT are compiled and run in a simulation environment. During simulation, the
various parts of the testbench interact with the DUT to verify its functionality. The simulation process
checks if the DUT behaves as expected under the given stimulus.
Simulator: A tool like ModelSim, VCS, or XSIM is used to run the simulation. These
simulators offer waveform viewers, command-line interfaces, and scripting capabilities to
monitor the simulation and analyze results.
Waveform Analysis: During simulation, the waveform viewer helps in inspecting the signals
and verifying the timing and functional correctness.
Assertions and functional coverage are critical for validating the correctness of the DUT.
Assertions help in detecting issues early in the design process and provide feedback for debugging.
Functional Coverage: This helps track which parts of the design have been exercised by the
testbench. SystemVerilog provides coverage constructs like covergroup, coverpoint, and cross
to measure functional coverage. Functional coverage ensures that all scenarios are tested and
helps identify untested regions of the design.
When a failure is encountered, debugging is an essential part of the verification process. Common
steps in debugging include:
Using assertions and coverage information makes debugging easier because it pinpoints areas of the
design that may need further attention.
7. Regression Testing
Once the design has been thoroughly tested and the issues resolved, a set of tests is usually run as a
regression to ensure that new changes do not introduce errors in the design. This is important for
ongoing development where the design might evolve and new features are added.
Regression Suites: A set of test cases is run automatically on each new version of the design
to ensure all features continue to work as expected.
Continuous Integration (CI): Often automated to run regression tests on every commit or
change to the codebase to detect issues as early as possible.
8. Verification Closure
Verification closure is the process of ensuring that all aspects of the design have been verified. This
includes:
Achieving Full Coverage: All coverage metrics should meet the specified goals. This
includes functional coverage and code coverage.
No Open Bugs: All bugs should be fixed, and no new issues should remain.
Sign-off: The design is considered verified when it satisfies the test plan and meets the
required performance and functional objectives.
Verification closure typically involves a sign-off report to provide documentation of the verification
results and the areas that have been covered.
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.
Some steps are accomplished automatically by the testbench, while others are manually determined
by the verification Engineer.
Directed Testing
Traditionally directed tests are used to verify the correctness of a design. Using this approach, the
verification engineer has to look at the hardware specification and write a verification plan with a list
of tests, each of which concentrated on a set of related features.
Armed with verification plan, the verification Engineer
• Write stimulus vectors that exercise these features in the DUT.
• Simulate the DUT with these vectors
• Manually review the resulting log files and waveforms to make sure the design does what you
expect.
Once the test works correctly, the verification engineer check off the test in the verification plan and
move to the next. Directed test is an incremental approach that makes a steady progress.
In directed testing, the test progress slope remains the same irrespective of the complexity of th
design. When the design complexity doubles, it takes twice as long to complete or requires twice as
many people. Neither of these situations is desirable. So it is needed to have a methodology that finds
bugs faster in order to reach the goal of 100% coverage.
Figure 1-2 shows the total design space and the features that get covered by directed test cases. In this
design space there are many features, some of which have bugs.
Testbench Components
In simulation, the testbench wraps around the DUT (Design under Test) by applying the stimulus to
DUT and record the responses from DUT through the monitors. So the primary components of a
testbench are
(i) DUT
(ii) Stimulus Generator
(iii) Monitors
Layered Testbnch
Layered testbench is a modern verification methodology that helps to make the verification task
easier. If the testbench has been written as a single routine that can randomly generate all types of
stimulus, both legal and illegal, plus inject errors with a multi-layer protocol, then the routine quickly
becomes complex and unmaintainable. Layered testbench organizes the testbench into multiple layers,
each with a specific responsibility, which improves the scalability, reusability, and maintainability of
the verification environment. The idea is to separate the different functionalities of the testbench into
logical layers, each layer interacting with the DUT (Device Under Test) in a structured manner.
SystemVerilog introduces several two-state data types to improve simulator performance and
reduce memory usage over four-state types. The simplest type is the bit, which is always unsigned.
There are four signed types: byte, shortint, int, and longint.
byte is a 8 bit signed variable which will take the value in the range of -128 to +127. Logic [7:0] and
bit [7:0] are 8 bit unsigned variable which will take its value in the range of 0 to 255. Signed variables
may cause unexpected results with randomization. Care has to be taken while connecting two-state
variables to the design under test, especially at its outputs. If the hardware tries to drive an X or Z,
these values are converted to a two-state value, and your testbench code may never know.
Fixed-Size Arrays
SystemVerilog offers several flavors of arrays beyond the single-dimension, fixed-size Verilog-1995
arrays. Many enhancements have been made to these classic arrays.
Verilog requires that the low and high array limits must be given in the declaration. Almost all arrays
use a low index of 0, so SystemVerilog uses the shortcut of just giving the array size, similar to C:
Example for Declaring fixed-size arrays
int lo_hi[0:15]; // 16 ints [0]..[15]
int c_style[16]; // 16 ints [0]..[15]
Multidimensional arrays
Multidimensional fixed-size arrays can be created by specifying the dimensions after the variable
name. This is an unpacked array. The following creates several two-dimensional arrays of integers, 8
entries by 4, and sets the last entry to 1. Multidimensional arrays were introduced in Verilog-2001, but
the compact declarations are new.
Example for Declaring and using multidimensional arrays
int array2 [0:7][0:3]; // Verbose declaration
int array3 [8][4]; // Compact declaration
array2[7][3] = 1; // Set last array element
SystemVerilog stores each element on a longword (32-bit) boundary. So a byte, shortint, and int are
all stored in a single longword, while a longint is stored in two longwords.
Example for Unpacked array declarations
bit [7:0] b_array[3]; // Unpacked
The unpacked array of bytes, b_array, is stored in three longwords.
An array can be initialized using an array literal that is an apostrophe and curly braces.1 You can
set some or all elements at once. Array values can be replicated by putting a count before the curly
braces.
Example for Initializing an array
int ascend[4] = ’{0,1,2,3}; // Initialize 4 elements
int decend[5];
int md[2][3] = ’{’{0,1,2}, ’{3,4,5}};
descend = ’{4,3,2,1,0}; // Set 5 elements
descend[0:2] = ’{5,6,7}; // Set first 3 elements
ascend = ’{4{8}}; // Four values of 8
Basic array operations — for and foreach
The most common way to manipulate an array is with a for or foreach loop. In a for loop, the loop
variable i is declared local to the for loop. The system verilog function $size returns the size of the
array. In the foreach statement, if the array name and an index in square brackets are specified, then
the system verilog automatically steps through all the elements of the array. The index variable is
local to the loop.
Example for Using arrays with for and foreach loops
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
In the syntax of the foreach statement for multidimensional arrays, instead of listing each subscript in
separate square brackets – [i][j] – they are combined with a comma – [i,j].
Example for Initialize and step through a multidimensional array
initial begin
$display("Initial value:");
foreach (md[i,j]) // Yes, this is the right syntax
$display("md[%0d][%0d] = %0d", i, j, md[i][j]);
$display("New value:");
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]);
end
Basic array operations – copy and compare
Aggregate compare and copy of arrays can be performed without loops. (An aggregate operation
works on the entire array as opposed to working onjust an individual element.) Comparisons are
limited to just equality and inequality.
Example shows several examples of compares. The ? : operator is a mini if-statement. Here it is
choosing between two strings.
Example for Array copy and compare operations
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;
// Change just one element
src[0] = 5;
// Are all values equal (no!)
$display("src %s dst", (src == dst) ? "==" : "!=");
// Are last elements 1-4 equal (yes!)
$display("src[1:4] %s dst[1:4]",
(src[1:4] == dst[1:4]) ? "==" : "!=");
End
Aggregate arithmetic operations such as addition cannot be performed on arrays. Arithmetic and
logical operations on arrays can be performed only using loops.
Packed arrays
For some data types, you may want both to access the entire value and also divide it into smaller
elements. For example, A 32-bit register has to be sometimes treated as four 8-bit values and at other
times as a single, unsigned value. A System Verilog treats packed array as both an array and a single
value. It is stored as a contiguous set of bits with no unusedspace, unlike an unpacked array.
Packed array examples
The packed bit and word dimensions are specified as part of the type, before the variable name. These
dimensions must be specified in the [lo:hi] format. The variable bytes is a packed array of four bytes,
which are stored in a single longword.
Example for Packed array declaration and usage
bit [3:0] [7:0] bytes; // 4 bytes packed into 32-bits
bytes = 32’hdead_beef;
$displayh(bytes,, // Show all 32-bits
bytes[3], // most significant byte "de"
bytes[3][7]); // most significant bit "1"
Packed and unpacked dimensions can be mixed. You may want to make an array that represents a
memory that can be accessed as bits, bytes, or longbytes words.
Example for Declaration for mixed packed/unpacked array
bit [3:0] [7:0] barray [3]; // Packed: 3x32-bit
barray[0] = 32’h0123_4567;
barray[0][3] = 8’h01;
barray[0][1][6] = 1’b1;
The variable bytes is a packed array of four bytes, which are stored in a single longword. barray is an
array of three of these elements.
A longword of data, barray[2] can be accessed with a single subscript. A byte of data, barray[0][3]
can be accessed with two subscripts. A single bit, barray[0][1][6] can be accessed with three
subscripts.
Dynamic Arrays
If the size of the array is fixed at compile time, then it is called fixed-size array. If the size of the array
is not known till the run time, it is called as dynamic arrays. System Verilog provides a dynamic array
that can be allocated and resized during simulation. A dynamic array is declared with empty word
subscripts []. This means that an array size is not given at compile time; instead, it is specified at run-
time. The array is initially empty, and the new[] operator is called to allocate space, passing in the
number of entries in the square brackets. If the name of an array is passed to the new[] operator, then
the values are copied into the new elements.
Example for using dynamic arrays
int dyn[], d2[]; // Empty dynamic arrays
initial begin
dyn = new[5]; // Allocate 5 elements
foreach (dyn[j])
dyn[j] = j; // Initialize the elements
d2 = dyn; // Copy a dynamic array
d2[0] = 5; // Modify the copy
$display(dyn[0],d2[0]); // See both values (0 & 5)
dyn = new[20](dyn); // Expand and copy
dyn = new[100]; // Allocate 100 new integers // Old values are lost
dyn.delete; // Delete all elements
end
Dynamic arrays have several specialized routines, such as delete and size. Fixed-size array can be
assigned to dynamic arrays as long as they have the same base type such as int. A dynamic array can
be assigned to a fixed array as long as they have the same number of elements. When a fixed-size
array is copied to a dynamic array, System Verilog calls new[] constructor to allocate space, and then
copies the values.
Queues
Queue is one of the new data type introduced by system verilog which provides easy searching and
sorting in a structure that is as fast as a fixed-size array but as versatile as a linked list. Like a dynamic
array, queues can grow and shrink, but with a queue you can easily add and remove elements
anywhere.
Example for queue operations
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 before 2
q.insert(3, b); // {0,1,2,3,4,5} Insert whole q.
q.delete(1); // {0,2,3,4,5} Delete elem. #1
// The rest of these are fast
q.push_front(6); // {6,0,2,3,4,5} Insert at front
j = q.pop_back; // {6,0,2,3,4} j = 5
q.push_back(8); // {6,0,2,3,4,8} Insert at back
j = q.pop_front; // {0,2,3,4,8} j = 6
foreach (q[i])
$display(q[i]);
end
When a queue is created, the SystemVerilog actually allocates extra space so that extra elements can
be added quickly. Queue automatically grows or shrinks based on the elements added or removed
from the queue. It is very efficient to push and pop elements from the front and back of a queue,
taking a fixed amount of time no matter how large the queue. Adding and deleting elements in the
middle is slower, especially for larger queues, as SystemVerilog has to shift up to half of the
elements. The contents of a fixed or dynamic array can be copied into a queue.
Associative Arrays
SystemVerilog offers associative arrays that store entries in a sparse matrix. This means that while
you can address a very large address space, SystemVerilog only allocates memory for an element
when it is wriiten to it. In the following picture, the associative array holds the values 0:3, 42, 1000,
4521, and 200,000. The memory used to store these is far less than would be needed to store a fixed or
dynamic array with 200,000 entries.
assoc[idx] = idx;
idx = idx << 1;
end
// Step through all index values with foreach
foreach (assoc[i])
$display("assoc[%h] = %h", i, assoc[i]);
// Step through all index values with functions
if (assoc.first(idx))
begin // Get first index
do
$display("assoc[%h]=%h", idx, assoc[idx]);
while (assoc.next(idx)); // Get next index
end
// Find and delete the first element
assoc.first(idx);
assoc.delete(idx);
end
Example above has the associative array, assoc, with very scattered elements: 1, 2, 4, 8, 16, etc. A
simple for loop cannot step through them; but it needs to use a foreach loop, or, if you wanted finer
control, you could use the first and next functions in a do...while loop. These functions modify the
index argument, and return 0 or 1 depending on whether any elements are left in the array.
Linked Lists
SystemVerilog provides a linked list data-structure that is analogous to the STL (Standard Template
Library in C++) List container. The container is defined as a parameterized class, meaning that it can
be customized to hold data of any type.
Array Methods
There are many array methods that can be used on any unpacked array types: fixed, dynamic, queue,
and associative. These routines can be as simple as giving the current array size to sorting the
elements.
Array reduction methods
A basic array reduction method takes an array and reduces it to a scalar. The most common reduction
method is sum, which adds together all the values in an array. Be careful of SystemVerilog‘s rules for
handling the width of operations. By default, if you add the values of a single-bit array, the result is a
single bit. But if you store the result in a 32-bit variable or compare it to a 32-bit variable,
SystemVerilog uses 32-bits when adding up the values.
Example for Creating the sum of an array
bit on[10]; // Array of single bits
int summ;
initial begin
for each (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
Memory usage
The simulation memory usage is reduced by using two-state elements.
Data sizes that are multiples of 32 bits must be chosen to avoid unused space.
Simulators usually store anything smaller in a 32-bit word.
For arrays with a thousand to a million active elements, fixed-size and dynamic arrays are the
most memory efficient.
Queues are slightly less efficient to access than fixed-size or dynamic arrays because of
additional pointers.
Modeling memories larger than a few megabytes should be done with an associative array.
Speed
Array type has to be chosen based on how many times it is accessed per clock cycle. Array
size and type matters only when the array has to be accessed often.
Fixed-size and dynamic arrays are stored in contiguous memory, so any element can be found
in the same amount of time, regardless of array size.
Queues have almost the same access time as a fixed-size or dynamic array for reads and
writes.
Associative arrays are the slowest as it requires more computation than other arrays.
Sorting
SystemVerilog can sort any single-dimension array (fixed-size, dynamic, and associative
arrays plus queues), based on how often the data is added to the array.
fixed-size or dynamic array can be chosen if the data is received all at once so that you only
have to allocate the array once.
Queue can be chosen if the data slowly dribbles in, as adding new elements to the head or tail
of the queue is very efficient.
Associative arrays can be chosen If you have values that are noncontiguous such as ‗{1, 10,
11, 50}, and are also unique.
Creating New Types with typedef
In system verilog new data types using the typedef statement. If an ALU that can be configured at
compile-time to use on 8, 16, 24, or 32-bit operands, then in Verilog a macro for the operand width
and another for the type can be created. In SystemVerilog a new datatype can be created with the
typedef statement.
Example for User-defined type-macro in Verilog
// Old Verilog style
`define OPSIZE 8
`define OPREG reg [`OPSIZE-1:0]
`OPREG op_a, op_b;
Example for User-defined type in SystemVerilog
// New SystemVerilog style
parameter OPSIZE = 8;
typedef reg [OPSIZE-1:0] opreg_t;
opreg_t op_a, op_b;
Creating User-Defined Structures
One of the biggest limitations of Verilog is the lack of data structures. In SystemVerilog a new
structure can be created using the struct statement, similar to C.
struct {bit [7:0] r, g, b;} pixel; creates a structure called pixel that has three unsigned bytes for
red, green, and blue.
typedef struct {bit [7:0] r, g, b;} pixel_s;
pixel_s my_pixel; creates a structure called pixel_s than can be shared to ports and routines.
Using the suffix ―_s‖ when declaring a struct makes it easier to share and reuse code
Union of several types
In hardware, the interpretation of a set of bits in a register may depend on the value of other bits. It is
possible to store data of two different types in the same location. For example
typedef union { int i; real f; } num_u;
num_u un;
un.f = 0.0; // set un in floating point format
un.i = 0; // set un in integer format
The above code creates a new datatype num_u of type union to store an integer and real number in the
same location to save memory. Unions are useful when it is frequently required to read and write a
register in several different formats.
Packed structures
SystemVerilog allows to have more control on how data is laid out in memory by using packed
structures. A packed structure is stored as a contiguous set of bits with no unused space.
typedef struct {bit [7:0] r, g, b;} pixel_s;
pixel_s my_pixel;
The struct for a pixel in the above example uses three data values which is stored in three longwords,
even though it only needs three bytes. But if it is specified as a packed structure, it will use the
smallest space possible as three bytes.
Example for Packed structure
typedef struct packed {bit [7:0] r, g, b;} pixel_p_s;
pixel_p_s my_pixel;
Packed structures are used when the underlying bits represent a numerical value, or when it is
required to reduce memory usage. For example several bit-fields are packed together to make a single
register
Enumerated Data Types
An enumeration creates a strong variable type that is limited to a set of specified names such as the
instruction opcodes or state machine values. The simplest enumerated type declaration contains a list
of constant names and one or more variables.
Example for a simple enumerated type
enum {RED, BLUE, GREEN} color;
Usually a named enumerated type is created to easily declare multiple variables, especially if these are
used as routine arguments or module ports. First the enumerated type is createed, and then the
variables of this enumerated type is created.
Example for Enumerated types
// Create data type for values 0, 1, 2
typedef enum {INIT, DECODE, IDLE} fsmstate_e;
fsmstate_e pstate, nstate; // declare typed variables
initial begin
case (pstate)
IDLE: nstate = INIT; // data assignment
INIT: nstate = DECODE;
default: nstate = IDLE;
endcase
$display("Next state is %0s",
nstate.name); // Use name function
end
The suffix ―_e‖ has to be used when declaring an enumerated type. Default values of Enumerated type
are set to integers starting at 0 and then increase.
Example for Specifying enumerated values
typedef enum {INIT, DECODE=2, IDLE} fsmtype_e;
The above example uses the default value of 0 for INIT, then 2 for DECODE, and 3 for IDLE.
Enumerated constants also follow the same scoping rules as variables. Consequently, if the same
name is used in several enumerated types (such as INIT in different state machines), they have to be
declared in different scopes such as modules, program blocks, routines, or classes.
Example for Incorrectly specifying enumerated values
typedef enum {FIRST=1, SECOND, THIRD} ordinal_e;
ordinal_e position;
Example for Correctly specifying enumerated values
typedef enum {ERR_O=0, FIRST=1, SECOND, THIRD} ordinal_e;
ordinal_e position;
Routines for enumerated types
SystemVerilog provides several functions for stepping through enumerated
types.
�first returns first member of the enumeration.
�last returns last member of the enumeration.
�next returns the next element of the enumeration.
�next(N) returns the Nth next element.
�prev returns the previous element of the enumeration.
�prev(N) returns the Nth previous element.
The functions next and prev wrap around when they reach the beginning or end of the enumeration.
Constants
There are several types of constants in SystemVerilog. The classic Verilog way to create a constant is
with a text macro. On the plus side, macros have global scope and can be used for bit field definitions
and type definitions. On the negative side, macros are global, so they can cause conflicts if you just
need a local constant. Lastly, a macro requires the ` character so that it will be recognized and
expanded.
In SystemVerilog, parameters can be declared at the $root level so they can be global. This approach
can replace many Verilog macros that were just being used as constants. typedef can be used to replace
those clunky macros.
The next choice is a parameter. A Verilog parameter was loosely typed and was limited in scope to a
single module. Verilog-2001 added typed parameters, but the limited scope kept parameters from
being widely used. SystemVerilog also supports the const modifier that allows you to make a variable
that can be initialized in the declaration but not written by procedural code.
Example for Declaring a const variable
initial begin
const byte colon = ":";
...
End
Strings
The SystemVerilog string type holds variable-length strings. An individual character is of type byte.
The elements of a string of length N are numbered 0 to N-1. Unlike C, there is no null character at the
end of a string, and any attempt to use the character ―\0‖ is ignored. Strings use dynamic memory
allocation, so it is not required to worry about running out of space to store the string.
The function getc(N) returns the byte at location N, while toupper returns an upper-case copy of the
string and tolower returns a lowercase copy. The curly braces {} are used for concatenation. The task
putc(M) writes a byte into a string at location M, that must be between 0 and the length as given by
len. The substr(start,end) function extracts characters from location start to end.
Example for String methods or functions
string s;
initial begin
s = "SystemVerilog";
$display(s.getc(0)); // Display: 83 (‘S’)
$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
// Create temporary string, note format
my_log($psprintf("%s %5d", s, 42));
end
task my_log(string message);
// Print a message to a log
$display("@%0d: %s", $time, message);
endtask