Queues

A queue is a variable-size, ordered collection of homogeneous elements. A queue supports constant time access to all its elements as well as constant time insertion and removal at the beginning or the end of the queue. Each element in a queue is identified by an ordinal number that represents its position within the queue, with 0 representing the first, and $ representing the last. A queue is analogous to a one-dimensional unpacked array that grows and shrinks automatically. Thus, like arrays, queues can be manipulated using the indexing, concatenation, slicing operator syntax, and equality operators. Queues are declared using the same syntax as unpacked arrays, but specifying $ as the array size. The maximum size of a queue can be limited by specifying its optional right bound (last index).
For example:

byte q1[$]; // A queue of bytes
string names[$] = { "Bob" }; // A queue of strings with one element
integer Q[$] = { 3, 2, 7 }; // An initialized queue of integers
bit q2[$:255]; // A queue whose maximum size is 256 bits

The empty array literal {} is used to denote an empty queue. If an initial value is not provided in the declaration, the queue variable is initialized to the empty queue.

Queue Operators

Queues support the same operations that can be performed on unpacked arrays, and using the same operators and rules except as defined below:

int q_[$] = { 2, 4, 8 };
int p_[$];
int e, pos;

e = q_[0]; // read the first (left-most) item
e = q_[$]; // read the last (right-most) item
q_[0] = e; // write the first item
p_ = q_; // read and write entire queue (copy)
q_ = { q_, 6 }; // insert ’6’ at the end (append 6)
q_ = { e, q_ }; // insert ’e’ at the beginning (prepend e)
q_ = q_[1:$]; // delete the first (left-most) item
q_ = q_[0:$-1]; // delete the last (right-most) item
q_ = q_[1:$-1]; // delete the first and last items
q_ = {}; // clear the queue (delete all items)
q_ = { q_[0:pos-1], e, q_[pos,$] }; // insert ’e’ at position pos
q_ = { q_[0:pos], e, q_[pos+1,$] }; // insert ’e’ after position pos

Unlike arrays, the empty queue, {}, is a valid queue and the result of some queue operations. The following rules govern queue operators:

  • Q[ a : b ] yields a queue with b – a + 1 elements.
  • If a > b then Q[a:b] yields the empty queue {}.
  • Q[ n : n ] yields a queue with one item, the one at position n. Thus, Q[ n : n ] === { Q[n] }.
  • If n lies outside Q’s range (n < 0 or n > $) then Q[n:n] yields the empty queue {}.
  • If either a or b are 4-state expressions containing X or Z values, it yields the empty queue {}.
  • Q[ a : b ] where a < 0 is the same as Q[ 0 : b ].
  • Q[ a : b ] where b > $ is the same as Q[ a : $ ].

Queue methods

In addition to the array operators, queues provide several built-in methods.

size()

The prototype for the size() method is:
function int size();

The size() method returns the number of items in the queue. If the queue is empty, it returns 0.

for ( int j = 0; j < q.size; j++ ) $display( q[j] );

insert()

The prototype of the insert() method is:
function void insert(int index, queue_type item);

The insert() method inserts the given item at the specified index position.

  • Q.insert(i, e) is equivalent to: Q = {Q[0:i-1], e, Q[i,$]}

delete()

The prototype of the delete() method is:
function void delete(int index);

The delete() method deletes the item at the specified index position.

  • Q.delete(i) is equivalent to: Q = {Q[0:i-1], Q[i+1,$]}

pop_front()

The prototype of the pop_front() method is:
function queue_type pop_front();

The pop_front() method removes and returns the first element of the queue.

              e = Q.pop_front() is equivalent to: e = Q[0]; Q = Q[1,$]

pop_back()

The prototype of the pop_back() method is:
function queue_type pop_back();

The pop_back() method removes and returns the last element of the queue.

e = Q.pop_back() is equivalent to: e = Q[$]; Q = Q[0,$-1]

push_front()

The prototype of the push_front() method is:
function void push_front(queue_type item);

The push_front() method inserts the given element at the front of the queue.

Q.push_front(e) is equivalent to: Q = {e, Q}

push_back()

The prototype of the push_back() method is:
function void push_back(queue_type item);

The push_back() method inserts the given element at the end of the queue.

  • Q.push_back(e) is equivalent to: Q = {Q, e}

<< Previous | Next >>

Associative arrays

When the size of the collection is unknown or the data space is sparse, an associative array is a better option. Associative arrays do not have any storage allocated until it is used, and the index expression is not restricted to integral expressions but can be of any type.
An associative array implements a lookup table of the elements of its declared type. The data type to be used as an index serves as the lookup key, and imposes an ordering.

The syntax to declare an associative array is:

data_type array_id [ index_type ];

where:

  • data_type is the data type of the array elements. Can be any type allowed for fixed-size arrays.
  • array_id is the name of the array being declared.
  • index_type is the data type to be used as an index, or *. If * is specified, then the array is indexed by any

integral expression of arbitrary size. An index type restricts the indexing expressions to a particular type.

Examples of associative array declarations are:

integer i_array[*]; // associative array of integer (unspecified index)
bit [20:0] array_b[string]; // associative array of 21-bit vector, indexed by string
event ev_array[myClass]; // associative array of event indexed by class myClass

Array elements in associative arrays are allocated dynamically; an entry is created the first time it is written. The associative array maintains the entries that have been assigned values and their relative order according to the index data type. Associative array elements are unpacked, meaning that other than copying or comparing arrays, you must select an individual element out of the array before using it in most expressions.

Wildcard index type

Example: int array_name [*];

Associative arrays that specify a wildcard index type have the following properties:

  • The array can be indexed by any integral data type. Since the indices can be of different sizes, the same numerical value can have multiple representations, each of a different size. SystemVerilog resolves this ambiguity by detecting the number of leading zeros and computing a unique length and representation for every value.
  • Non-integral index types are illegal and result in a type check error.
  • A 4-state Index containing X or Z is invalid.
  • Indices are unsigned.
  • Indexing expressions are self-determined; signed indices are not sign extended.
  • A string literal index is auto-cast to a bit-vector of equivalent size.
  • The ordering is numerical (smallest to largest).

String index

Example: int array_name [ string ];

Associative arrays that specify a string index have the following properties:

  • Indices can be strings or string literals of any length. Other types are illegal and shall result in a type check error.
  • An empty string “” index is valid.
  • The ordering is lexicographical (lesser to greater).

Class index

Example: int array_name [ some_Class ];

Associative arrays that specify a class index have the following properties:

  • Indices can be objects of that particular type or derived from that type. Any other type is illegal and shall result in a type check error.
  • A null index is valid.
  • The ordering is deterministic but arbitrary.

Integer (or int) index

Example: int array_name [ integer ];

Associative arrays that specify an integer index have the following properties:

  • Indices can be any integral expression.
  • Indices are signed.
  • A 4-state index containing X or Z is invalid.
  • Indices smaller than integer are sign extended to 32 bits.
  • Indices larger than integer are truncated to 32 bits.
  • The ordering is signed numerical.

Signed packed array

Example: typedef bit signed [4:1] Nibble;

int array_name [ Nibble ];

Associative arrays that specify a signed packed array index have the following properties:

  • Indices can be any integral expression.
  • Indices are signed.
  • Indices smaller than the size of the index type are sign extended.
  • Indices larger than the size of the index type are truncated to the size of the index type.
  • The ordering is signed numerical.

Unsigned packed array or packed struct

Example: typedef bit [4:1] Nibble;

int array_name [ Nibble ];

Associative arrays that specify an unsigned packed array index have the following properties:

  • Indices can be any integral expression.
  • Indices are unsigned.
  • A 4-state Index containing X or Z is invalid.
  • Indices smaller than the size of the index type are zero filled.
  • Indices larger than the size of the index type are truncated to the size of the index type.
  • The ordering is numerical

Associative array methods

In addition to the indexing operators, several built-in methods are provided that allow users to analyze and manipulate associative arrays, as well as iterate over its indices or keys.

num()

The syntax for the num() method is:
function int num();

The num() method returns the number of entries in the associative array. If the array is empty, it returns 0.

int imem[*];
imem[ 2’b3 ] = 1;
imem[ 16’hffff ] = 2;
imem[ 4b’1000 ] = 3;
$display( "%0d entries\n", imem.num );
// prints “3 entries”

delete()

The syntax for the delete() method is:
function void delete( [input index] );

Where index is an optional index of the appropriate type for the array in question. If the index is specified, then the delete() method removes the entry at the specified index. If the entry to be deleted does not exist, the method issues no warning.

If the index is not specified, then the delete() method removes all the elements in the array.

int map[ string ];
map[ "hello" ] = 1;
map[ "sad" ] = 2;
map[ "world" ] = 3;

map.delete( "sad" ); // remove entry whose index is “sad” from “map”
map.delete; // remove all entries from the associative array “map”

exists()

The syntax for the exists() method is:
function int exists( input index );

Where index is an index of the appropriate type for the array in question. The exists() function checks if an element exists at the specified index within the given array. It returns 1 if the element exists, otherwise it returns 0.

if ( map.exists( "hello" ))
map[ "hello" ] += 1;
else
map[ "hello" ] = 0;

first()

The syntax for the first() method is:
function int first( ref index );

Where index is an index of the appropriate type for the array in question. The first() method assigns to the given index variable the value of the first (smallest) index in the associative array. It returns 0 if the array is empty, and 1 otherwise.

string s;
if ( map.first( s ) )
$display( "First entry is : map[ %s ] = %0d\n", s, map[s] );

last()

The syntax for the last() method is:
function int last( ref index );

Where index is an index of the appropriate type for the array in question. The last() method assigns to the given index variable the value of the last (largest) index in the associative array. It returns 0 if the array is empty, and 1 otherwise.

string s;
if ( map.last( s ) )
$display( "Last entry is : map[ %s ] = %0d\n", s, map[s] );

next()

The syntax for the next() method is:
function int next( ref index );

Where index is an index of the appropriate type for the array in question. The next() method finds the entry whose index is greater than the given index. If there is a next entry, the index variable is assigned the index of the next entry, and the function returns 1. Otherwise, the index is unchanged, and the function returns 0.

string s;
if ( map.first( s ) )
do
$display( "%s : %d\n", s, map[ s ] );
while ( map.next( s ) );

prev()

The syntax for the prev() method is:
function int prev( ref index );

Where index is an index of the appropriate type for the array in question. The prev() function finds the entry whose index is smaller than the given index. If there is a previous entry, the index variable is assigned the index of the previous entry, and the function returns 1. Otherwise, the index is unchanged, and the function returns 0.

string s;
if ( map.last( s ) )
do
$display( "%s : %d\n", s, map[ s ] );
while ( map.prev( s ) );

If the argument passed to any of the four associative array traversal methods first, last, next, and prev are smaller than the size of the corresponding index, then the function returns –1 and shall copy only as much data as can fit into the argument. For example:

string aa[*];
byte ix;
int status;
aa[ 1000 ] = "a";
status = aa.first( ix );

// status is –1
// ix is 232 (least significant 8 bits of 1000)

<< Previous | Next >>

Dynamic arrays

A dynamic array is one dimension of an unpacked array whose size can be set or changed at runtime. The space for a dynamic array doesn’t exist until the array is explicitly created at runtime.
The syntax to declare a dynamic array is:

data_type array_name [];

where data_type is the data type of the array elements. Dynamic arrays support the same types as fixed-size arrays.
For example:

bit [3:0] nibble[]; // Dynamic array of 4-bit vectors
integer mem[]; // Dynamic array of integers

The new[] operator is used to set or change the size of the array.
The size() built-in method returns the current size of the array.
The delete() built-in method clears all the elements yielding an empty array (zero size).

new[]

The built-in function new allocates the storage and initializes the newly allocated array elements either to their default initial value or to the values provided by the optional argument.

size()

The prototype for the size() method is:
function int size();

The size() method returns the current size of a dynamic array, or zero if the array has not been created.

int j = addr.size;

addr = new[ addr.size() * 4 ] (addr); // quadruple addr array

Note: The size method is equivalent to $length( addr, 1 ).

delete()

The prototype for the delete() method is:
function void delete();

The delete() method empties the array, resulting in a zero-sized array.

int ab [] = new[ N ]; // create a temporary array of size N

ab.delete; // delete the array contents

$display( "%d", ab.size ); // prints 0

Array assignment

Assigning to a fixed-size unpacked array requires that the source and the target both be arrays with the same number of unpacked dimensions and the length of each dimension be the same. The assignment is done by assigning each element of the source array to the corresponding element of the target array, which requires that the source and target arrays be of compatible types. Compatible types are types that are assignment-compatible.

Assigning fixed-size unpacked arrays of unequal size to one another shall result in a type check error.

int A[10:1]; // fixed-size array of 10 elements

int B[0:9]; // fixed-size array of 10 elements

int C[24:1]; // fixed-size array of 24 elements

A = B; // ok. Compatible type and same size

A = C; // type check error: different sizes

An array of wires can be assigned to an array of variables having the same number of unpacked dimensions and the same length for each of those dimensions, and vice-versa.

wire [31:0] W [9:0];
assign W = A;
initial #10 B = W;

A dynamic array can be assigned to a one-dimensional fixed-size array of a compatible type if the size of the dynamic array is the same as the length of the fixed-size array dimension. Unlike assigning with a fixed-size array, this operation requires a run-time check that can result in an error.

int A[100:1]; // fixed-size array of 100 elements
int B[] = new[100]; // dynamic array of 100 elements
int C[] = new[8]; // dynamic array of 8 elements
A = B; // OK. Compatible type and same size
A = C; // type check error: different sizes

A dynamic array or a one-dimensional fixed-size array can be assigned to a dynamic array of a compatible type. In this case, the assignment creates a new dynamic array with a size equal to the length of the fixed-size array. For example:

int A[100:1]; // fixed-size array of 100 elements
int B[]; // empty dynamic array
int C[] = new[8]; // dynamic array of size 8
B = A; // ok. B has 100 elements
B = C; // ok. B has 8 elements

The last statement above is equivalent to:

B = new[ C.size ] (C);

Similarly, the source of an assignment can be a complex expression involving array slices or concatenations.
For example:

string d[1:5] = { "a", "b", "c", "d", "e" };
string p[];
p = { d[1:3], "hello", d[4:5] };

The preceding example creates the dynamic array p with contents: “a”, “b”, “c”, “hello”, “d”, “e”.

Arrays as arguments

Arrays can be passed as arguments to tasks or functions. The rules that govern array argument passing by value are the same as for array assignment. When an array argument is passed by value, a copy of the array is passed to the called task or function. This is true for all array types: fixed-size, dynamic, or associative.

Note that unsized dimensions can occur in dynamic arrays and in formal arguments of import DPI functions. If one dimension of a formal is unsized, then any size of the corresponding dimension of an actual is accepted.

For example, the declaration:

task fun(int a[3:1][3:1]);

declares task fun that takes one argument, a two-dimensional array with each dimension of size three. A call to fun must pass a two-dimensional array and with the same dimension size 3 for all the dimensions. For example,

given the above description for fun, consider the following actuals:

int b[3:1][3:1]; // OK: same type, dimension, and size

int b[1:3][0:2]; // OK: same type, dimension, & size (different ranges)

reg b[3:1][3:1]; // OK: assignment compatible type

event b[3:1][3:1]; // error: incompatible type

int b[3:1]; // error: incompatible number of dimensions

int b[3:1][4:1]; // error: incompatible size

A subroutine that accepts a dynamic array can be passed a dynamic array of a compatible type or a one-dimensional fixed-size array of a compatible type

For example, the declaration:

task ABC( string arr[] );

declares a task that accepts one argument, a dynamic array of strings. This task can accept any one-dimensional array of strings or any dynamic array of strings.

An import DPI function that accepts a one-dimensional array can be passed a dynamic array of a compatible type and of any size if formal is unsized, and of the same size if formal is sized. However, a dynamic array cannot be passed as an argument if formal is an unsized output.

<< Previous | Next >>

Arrays

Introduction

An array is a collection of variables, all of the same type, and accessed using the same name plus one or more indices.

In Verilog-2001, arrays are indexed from left-bound to right-bound. If they are vectors, they can be assigned as a single unit, but not if they are arrays. Verilog-2001 allows multiple dimensions.

In Verilog-2001, all data types can be declared as arrays. The reg, wire and all other net types can also have a vector width declared. A dimension declared before the object name is referred to as the “vector width” dimension.

The dimensions declared after the object name are referred to as the “array” dimensions.

reg [7:0] r1 [1:256]; // [7:0] is the vector width, [1:256] is the array size

SystemVerilog uses the term “packed array” to refer to the dimensions declared before the object name (what Verilog-2001 refers to as the vector width). The term “unpacked array” is used to refer to the dimensions declared after the object name.

bit [7:0] c1; // packed array

real u [7:0]; // unpacked array

SystemVerilog enhances packed arrays by allowing multiple dimensions. SystemVerilog adds the ability to procedurally change the size of one of the dimensions of an unpacked array. Fixed-size unpacked arrays can be multi-dimensional and have fixed storage allocated for all the elements of the array. Each dimension of an unpacked array can be declared as having a fixed or un-fixed size.

A dynamic array allocates storage for elements at runtime along with the option of changing the size of one of its dimensions.

An associative array allocates storage for elements individually as they are written. Associative arrays can be indexed using arbitrary data types.

A queue type of array grows or shrinks to accommodate the number of elements written to the array at runtime.

Packed and unpacked arrays

A packed array is a mechanism for subdividing a vector into subfields which can be conveniently accessed as array elements. Consequently, a packed array is guaranteed to be represented as a contiguous set of bits.

An unpacked array may or may not be so represented. A packed array differs from an unpacked array in that when a packed array appears as a primary, it is treated as a single vector.

If a packed array is declared as signed, then the array viewed as a single vector shall be signed. The individual elements of the array are unsigned unless they are of a named type declared as signed. A part-select of a packed array shall be unsigned.

Packed arrays allow arbitrary length integer types, so a 48 bits integer can be made up of 48 bits. These integers can then be used for 48 bits arithmetic. The maximum size of a packed array can be limited but shall be at least 65536 (216) bits.

Packed arrays can only be made of the single bit types (bit, logic, reg, wire, and the other net types) and recursively other packed arrays and packed structures.

Integer types with predefined widths cannot have packed array dimensions declared. These types are byte, shortint, int, longint, and integer. An integer type with a predefined width can be treated as a single-dimension packed array. The packed dimensions of these integer types shall be numbered down to 0, such that the right-most index is 0.

byte c2; // same as bit [7:0] c2;

integer i1; // same as logic signed [31:0] i1;

Unpacked arrays can be made of any type. System Verilog enhances fixed-size unpacked arrays in that in addition to all other variable types, unpacked arrays can also be made of object handles and events.

System Verilog accepts a single number, as an alternative to a range, to specify the size of an unpacked array, like C. That is, [size] becomes the same as [0:size-1]. For example:

int Array[8][32]; is the same as: int Array[0:7][0:31];

The following operations can be performed on all arrays, packed or unpacked. The examples provided with these rules assume that A and B are arrays of the same shape and type.

  • Reading and writing the array, e.g., A = B
  • Reading and writing a slice of the array, e.g., A[i:j] = B[i:j]
  • Reading and writing a variable slice of the array, e.g., A[x+:c] = B[y+:c]
  • Reading and writing an element of the array, e.g., A[i] = B[i]
  • Equality operations on the array or slice of the array, e.g. A==B, A[i:j] != B[i:j]

The following operations can be performed on packed arrays, but not on unpacked arrays. The examples provided with these rules assume that A is an array.

  • Assignment from an integer, e.g., A = 8’b11111111;
  • Treatment as an integer in an expression, e.g., (A + 3)

If an unpacked array is declared as signed, then this applies to the individual elements of the array, since the whole array cannot be viewed as a single vector.

When assigning to an unpacked array, the source and target must be arrays with the same number of unpacked dimensions, and the length of each dimension must be the same. Assignment to an unpacked array is done by assigning each element of the source unpacked array to the corresponding element of the target unpacked array. Note that an element of an unpacked array can be a packed array.

For the purposes of assignment, a packed array is treated as a vector. Any vector expression can be assigned to any packed array. The packed array bounds of the target packed array do not affect the assignment. A packed array cannot be directly assigned to an unpacked array without an explicit cast.

Multiple dimensions

Like Verilog memories, the dimensions following the type set the packed size. The dimensions following the instance set the unpacked size.

bit [3:0] [7:0] john [1:10]; // 10 entries of 4 bytes (packed into 32 bits)

can be used as follows:

john[9] = john[8] + 1; // 4 byte add
john[7][3:2] = john[6][1:0]; // 2 byte copy

<< Previous | Next >>

Casting

A data type can be changed by using a cast ( ) operation. The expression to be cast must be enclosed in parentheses or within concatenation or replication braces and is self-determined.

int’(2.0 * 3.0)
shortint’{8’hFA,8’hCE}

A positive decimal number as a data type means a number of bits to change the size.

17’(x - 2)

The signedness can also be changed.

signed’(x)

A user-defined type can be used.

mytype’(x)

The expression inside the cast must be an integral value when changing the size or sign. When changing the size, the sign passes through unchanged. When changing the sign, the size passes through unchanged. When casting to a predefined type, the prefix of the cast must be the predefined type keyword. When casting to a user-defined type, the prefix of the cast must be the user-defined type identifier.

When a shortreal is converted to an int or to 32 bits, its value is rounded, as in Verilog. Therefore, the conversion can lose information. To convert a shortreal to its underlying bit representation without a loss of information, use $shortrealtobits. To convert from the bit representation of a shortreal value into a shortreal, use $bitstoshortreal.

Structures can be converted to bits preserving the bit pattern, which means they can be converted back to the same value without any loss of information. When unpacked data is converted to the packed representation, the order of the data in the packed representation is such that the first field in the structure occupies the most significant bits. The effect is the same as a concatenation of the data items (struct fields or array elements) in order. The type of the elements in an unpacked structure or array must be valid for a packed representation in order to be cast to any other type, whether packed or unpacked.

The following example demonstrates how the $bits attribute is used to obtain the size of a structure in bits which facilitates the conversion of the structure into a packed array:

typedef struct {
bit isfloat;
union { int i; shortreal f; } n;
// anonymous type
} tagged_st; // named structure
typedef bit [$bits(tagged_st) - 1 : 0] tagbits; // tagged_st defined above
tagged_st a [7:0]; // unpacked array of structures
tagbits t = tagbits’(a[3]); // convert structure to array of bits
a[4] = tagged_st’(t); // convert array of bits back to structure

Note that the bit data type loses X values. If these are to be preserved, the logic type should be used instead.

The size of a union in bits is the size of its largest member. The size of a logic in bits is 1.

For compatibility, the Verilog functions $itor, $rtoi, $bitstoreal, $realtobits, $signed, $unsigned can also be used.

$cast dynamic casting

SystemVerilog provides the $cast system task to assign values to variables that might not ordinarily be valid because of differing data type. $cast can be called as either a task or a function.

The syntax for $cast is:

function int $cast( singular dest_var, singular source_exp );
or
task $cast( singular dest_var, singular source_exp );

The dest_var is the variable to which the assignment is made.
The source_exp is the expression that is to be assigned to the destination variable.

Use of $cast as either a task or a function determines how invalid assignments are handled.

When called as a task, $cast attempts to assign the source expression to the destination variable. If the assignment is invalid, a runtime error occurs and the destination variable is left unchanged.

When called as a function, $cast attempts to assign the source expression to the destination variable, and returns 1 if the cast is legal. If the cast fails, the function does not make the assignment and returns 0. When called as a function, no runtime error occurs, and the destination variable is left unchanged.

It’s important to note that $cast performs a run-time check. No type checking is done by the compiler, except to check that the destination variable and source expression are singulars.

Example:

typedef enum { red, green, blue, yellow, white, black } Colors;
Colors col;
$cast( col, 2 + 3 );

This example assigns the expression (5 => black) to the enumerated type. Without $cast, or the static compile-time cast described below, this type of assignment is illegal.

The following example shows how to use the $cast to check if an assignment will succeed:

if ( ! $cast( col, 2 + 8 ) ) // 10: invalid cast

$display( "Error in cast" );

Alternatively, the preceding examples can be cast using a static SystemVerilog cast operation:
Example:

col = Colors’(2 + 3);

However, this is a compile-time cast, i.e, a coercion that always succeeds at run-time, and does not provide for error checking or warn if the expression lies outside the enumeration values.

Allowing both types of casts gives full control to the user. If users know that it is safe to assign certain expressions to an enumerated variable, the faster static compile-time cast can be used. If users need to check if the expression lies within the enumeration values, it is not necessary to write a lengthy switch statement manually, the compiler automatically provides that functionality via the $cast function. By allowing both types of casts, users can control the time/safety trade-offs.

Bit-stream casting

Type casting can also be applied to unpacked arrays and structs. It is thus possible to convert freely between bit-stream types using explicit casts. Types that can be packed into a stream of bits are called bit-stream types.
A bit-stream type is a type consisting of the following:

  • Any integral, packed, or string type
  • Unpacked arrays, structures, or classes of the above types
  • Dynamically-sized arrays (dynamic, associative, or queues) of any of the above types

This definition is recursive, for example, a structure containing a queue of int is a bit-stream type.
Assuming A is of bit-stream type source_t and B is of bit-stream type dest_t, it is legal to convert A into B by an explicit cast:

B = dest_t’(A);

The conversion from A of type source_t to B of type dest_t proceeds in two steps:

1) Conversion from source_t to a generic packed value containing the same number of bits as source_t. If source_t contains any 4-state data, the entire packed value is 4-state; otherwise, it is 2-state.

2) Conversion from the generic packed value to dest_t. If the generic packed value is a 4-state type and parts of dest_t designate 2-state types then those parts in dest_t are assigned as if cast to a 2-state.

If both source_t and dest_t are fixed-sized unpacked types of different sizes then a cast generates a compile-time error. If source_t or dest_t contain dynamically-sized types then a difference in their sizes will generate an error either at compile time or run time, as soon as it is possible to determine the size mismatch.

For example:

// Illegal conversion from 24-bit struct to int (32 bits) – compile time error
struct {bit[7:0] a; shortint b;} a;
int b = int’(a);

// Illegal conversion from 20-bit struct to int (32 bits) – run time error
struct {bit a[$]; shortint b;} a = {{1,2,3,4}, 67};
int b = int’(a);

// Illegal conversion from int (32 bits) to struct dest_t (25 or 33 bits),
// compile time error
typedef struct {byte a[$]; bit b;} dest_t;
int a;
dest_t b = dest_t’(a);

<< Previous | Next >>

Class

A class is a collection of data and a set of subroutines that operate on that data. The data in a class are referred to as class properties, and its subroutines are called methods. The class properties and methods, taken together, define the contents and capabilities of a class instance or object.

class_declaration ::=

[ virtual ] class [ lifetime ] class_identifier [ parameter_port_list ] [ extends class_type [ ( list_of_arguments ) ] ];

{ class_item }

endclass [ : class_identifier]

The object-oriented class extension allows objects to be created and destroyed dynamically. Class instances, or objects, can be passed around via object handles, which add a safe-pointer capability to the language. An object can be declared as an argument with direction input, output, inout, or ref. In each case, the argument copied is the object handle, not the contents of the object.

A Class is declared using the classendclass keywords. For example:

class Packet;

    int address; // Properties are address, data, and crc

    bit [63:0] data;

    shortint crc;

    Packet next; // Handle to another Packet

    function new(); // Methods are send and new

    function bit send();

endclass : Packet

<< Previous | Next >>

Structures and unions

Structure and union declarations follow the C syntax but without the optional structure tags before the ‘{‘.

struct { bit [7:0] opcode; bit [23:0] addr; } REG; // anonymous structure defines variable REG

REG.opcode = 1; // set field opcode in REG.

Some additional examples of declaring structure and unions are:

typedef struct {
bit [7:0] opcode;
bit [23:0] addr;
} REG ; // named structure type
REG REG1; // define variable

typedef union { int i; shortreal f; } num; // named union type
num n;
n.f = 0.0; // set n in floating point format

typedef struct {
bit isfloat;
union { int i; shortreal f; } n; // anonymous type
} tagged_st; // named structure
tagged_st a[9:0]; // array of structures

A structure can be assigned as a whole and passed to or from a function or task as a whole. A packed structure consists of bit fields, which are packed together in memory without gaps. This means that they are easily converted to and from bit vectors. An unpacked structure has an implementation-dependent packing, normally matching the C compiler.

Like a packed array, a packed structure can be used as a whole with arithmetic and logical operators. The first member specified is the most significant and subsequent members follow in decreasing significance. The structures are declared using the packed keyword, which can be followed by the signed or unsigned keywords, according to the desired arithmetic behavior. The default is unsigned:

struct packed signed {
int a;
shortint b;
byte c;
bit [7:0] d;
} pack1; // signed, 2-state

struct packed unsigned {
time a;
integer b;
logic [31:0] c;
} pack2; // unsigned, 4-state

If any data type within a packed structure is 4-state, the whole structure is treated as 4-state. Any 2-state members are converted as if cast. One or more bits of a packed structure can be selected as if it were a packed array, assuming an [n-1:0] numbering.

Non-integer data types, such as real and shortreal, are not allowed in packed structures or unions nor are in unpacked arrays.
A packed structure can be used with a typedef.

typedef struct packed { // default unsigned
bit [3:0] GFC;
bit [7:0] VPI;
bit [11:0] VCI;
bit CLP;
bit [3:0] PT ;
bit [7:0] HEC;
bit [47:0] [7:0] Payload;
bit [2:0] filler;
} s_atmcell;

A packed union shall contain members that must be packed structures or packed arrays or integer data types all of the same size (in contrast to an unpacked union, where the members can be of different sizes). This ensures that you can read back a union member that was written as another member. A packed union can also be used as a whole with arithmetic and logical operators, and its behavior is determined by the signed or unsigned keyword, the latter being the default. If a packed union contains a 2-state member and a 4-state member, the entire union is 4-state. There is an implicit conversion from 4-state to 2-state when reading and from 2-state to 4-state when writing the 2-state member.

For example, a union can be accessible with different access widths:

typedef union packed { // default unsigned
s_atmcell acell;
bit [423:0] bit_slice;
bit [52:0][7:0] byte_slice;
} u_atmcell;
u_atmcell u1;
byte b; bit [3:0] nib;
b = u1.bit_slice[415:408]; // same as b = u1.byte_slice[51];
nib = u1.bit_slice [423:420]; // same as nib = u1.acell.GFC;

Note that writing one member and reading another is independent of the byte ordering of the machine, unlike a normal union of normal structures, which are C-compatible and have members in ascending address order.

The signing of unpacked structures is not allowed. The following declaration would be considered illegal:

typedef struct signed {
int f1 ;
logic f2 ;
} IllegalSignedUnpackedStructType; // illegal declaration

<< Previous | Next >>

Enumerations

An enumerated type declares a set of integral named constants. Enumerated data types provide the capability to abstractly declare strongly typed variables without either a data type or data value(s) and later add the required data type and value(s) for designs that require more definition. Enumerated data types also can be easily referenced or displayed using the enumerated names as opposed to the enumerated values.

In the absence of a data type declaration, the default data type shall be int. Any other data type used with enumerated types shall require an explicit data type declaration.

An enumerated type defines a set of named values. In the following example, color1 and color2 are defined to be variables of the anonymous (unnamed) enumerated int type that includes the three members: red, yellow and green.

enum {red, yellow, green} color1, color2; // anonymous int type

An enumerated name with x or z assignments assigned to an enum with no explicit data type or an explicit 2-state declaration shall be a syntax error.

// Syntax error: XX=2’bx in below example

enum {IDLE, XX=’x, S1=2’b01, S2=2’b10} state, next;

An enum declaration of a 4-state type, such as integer, that includes one or more names with x or z assignments shall be permitted.

// Correct: IDLE=0, XX=’x, S1=1, S2=2

enum integer {IDLE, XX=’x, S1=’b01, S2=’b10} state, next;

An unassigned enumerated name that follows an enum name with x or z assignments shall be a syntax error.

// Syntax error: IDLE=2’b00, XX=2’bx, S1=??, S2=??

enum integer {IDLE, XX=’x, S1, S2} state, next;

The values can be cast to integer types, and increment from an initial value of 0. This can be overridden.

enum {bronze=3, silver, gold} medal; // silver=4, gold=5

The values can be set for some of the names and not set for other names. The optional value of an enum named constant is an elaboration time constant expression and can include references to parameters, local parameters, genvars, other enum named constants, and constant functions of these. Hierarchical names and const variables are not allowed. A name without a value is automatically assigned an increment of the value of the previous name.

enum {a=3, b=7, c} alphabet; // c is automatically assigned the increment-value of 8

If an automatically incremented value is assigned elsewhere in the same enumeration, this shall be a syntax error.

enum {a=0, b=7, c, d=8} alphabet; // Syntax error: c and d are both assigned 8

If the first name is not assigned a value, it is given the initial value of 0.

enum {a, b=7, c} alphabet; // a=0, b=7, c=8

Any enumeration encoding value that is outside the representable range of the enum shall be an error. If any of the enum members are defined with a different-sized constant, this shall be a syntax error.

enum bit [3:0] {bronze='h3, silver, gold='h5} medal; // Correct declaration – bronze and gold are unsized

enum bit [3:0] {bronze=4'h3, silver, gold=4'h5} medal; // Correct declaration – bronze and gold sizes are redundant

enum bit [3:0] {bronze=5'h13, silver, gold=3'h5} medal; // Error in the bronze and gold member declarations

Defining new data types as enumerated types

A type name can be given so that the same type can be used in many places.

typedef enum {NO, YES} boolean;

boolean myvar; // named type

Enumerated types in numerical expressions

Elements of enumerated type variables can be used in numerical expressions. The value used in the expression is the numerical value associated with the enumerated value. For example:

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

Colors col;

integer a, b;

a = blue * 3;

col = yellow;

b = col + green;

From the previous declaration, blue has the numerical value 2. This example assigns a value of 6 (2*3), and it assigns b a value of 4 (3+1). An enum variable or identifier used as part of an expression is automatically cast to the base type of the enum declaration (either explicitly or using int as the default). An assignment to an enum variable from an expression other than the same type shall require a cast. Casting to an enum type shall cause a conversion of the expression to its base type without checking the validity of the value.

typedef enum {Red, Green, Blue} Colors;

typedef enum {Mo,Tu,We,Th,Fr,Sa,Su} Week;

Colors C;

Week W;

int I;

C = Colors’(C+1);               // C is converted to an integer, then added to one, then converted back to a Colors type

C = C + 1; C++; C+=2; C = I; // Illegal because they would all be assignments of expressions without a cast

C = Colors’(Su);                // Legal; puts an out of range value into C

I = C + W;                       // Legal; C and W are automatically cast to int

SystemVerilog includes a set of specialized methods to enable iterating over the values of enumerated types.

first() :

The prototype for the first() method is:

function enum first();

The first() method returns the value of the first member of the enumeration.

last() :

The prototype for the last() method is:

function enum last();

The last() method returns the value of the last member of the enumeration.

next() :

The prototype for the next() method is:

function enum next( int unsigned N = 1 );

The next() method returns the Nth next enumeration value (default is the next one) starting from the current value of the given variable. A wrap to the start of the enumeration occurs when the end of the enumeration is reached. If the given value is not a member of the enumeration, the next() method returns the first member.

prev() :

The prototype for the prev() method is:

function enum prev( int unsigned N = 1 );

The prev() method returns the Nth previous enumeration value (default is the previous one) starting from the current value of the given variable. A wrap to the end of the enumeration occurs when the start of the enumeration is reached. If the given value is not a member of the enumeration, the prev() method returns the last member.

num() :

The prototype for the num() method is:

function int num();

The num() method returns the number of elements in the given enumeration.

name() :

The prototype for the name() method is:

function string name();

The name() method returns the string representation of the given enumeration value. If the given value is not a member of the enumeration, the name() method returns the empty string.

<< Previous | Next >>

User-defined types

type_declaration ::=

typedef data_type type_identifier variable_dimension ;

| typedef interface_instance_identifier . type_identifier type_identifer ;

| typedef [ enum | struct | union | class ] type_identifier ;

The user can define a new type using typedef.

typedef int Myint;

This can then be instantiated as:

Myint a, b;

A type can be used before it is defined, provided it is first identified as a type by an empty typedef:

typedef Mytype;
Mytype M = 1;
typedef int Mytype;

Note that this does not apply to enumeration values, which must be defined before they are used.

User-defined type identifiers have the same scoping rules as data identifiers, except that hierarchical reference to type identifiers shall not be allowed. References to type identifiers defined within an interface through ports are allowed provided they are locally re-defined before being used.

interface intf_i;
typedef int data_t;
endinterface

module sub(intf_i p)
typedef p.data_t my_data_t;
my_data_t data;  // type of ’data’ will be int when connected to interface above
endmodule

User-defined type names must be used for complex data types in casting (see Section 3.14, below), which only allows simple type names, and as type parameter values when unpacked array types are used. Sometimes a user-defined type needs to be declared before the contents of the type has been defined. This is of use with user-defined types derived from enum, struct, union, and class.

For examples :

typedef enum type_declaration_identifier;
typedef struct type_declaration_identifier;
typedef union type_declaration_identifier;
typedef class type_declaration_identifier;
typedef type_declaration_identifier;

Note that, while this is useful for coupled definitions of classes, it cannot be used for coupled definitions of structures, since structures are statically declared and there is no support for pointers to structures.

The last form shows that the type of the user-defined type does not have to be defined in the forward declaration.

A typedef inside a generate shall not define the actual type of a forward definition that exists outside the
scope of the forward definition.

<< Previous | Next >>

Event data types

The event data type is an enhancement over Verilog named events. SystemVerilog events provide a handle to a synchronization object. Like Verilog, event variables can be explicitly triggered and waited for. Furthermore, SystemVerilog events have a persistent triggered state that lasts for the duration of the entire time step. In addition, an event variable can be assigned another event variable or the special value null. When assigned another event variable, both event variables refer to the same synchronization object. When assigned null, the association between the synchronization object and the event variable is broken. Events can be passed as arguments to tasks.

The syntax to declare an event is:

event variable_name [= initial_value];

Where variable_name is a valid identifier and the optional initial_value can be another event variable or the special value null.

If an initial value is not specified then the variable is initialized to a new synchronization object.

If the event is assigned null, the event becomes nonblocking, as if it were permanently triggered.

Examples:

event E1;               // declare a new event called E1

event E2 = E1;  // declare E2 as alias to E1

event empty = null    // event variable with no synchronization object

<< Previous | Next >>