SystemVerilog for Design Edition 2 Chapter 5

发布时间 2023-05-22 23:50:36作者: sasasatori

SystemVerilog for Design Edition 2 Chapter 5

SystemVerilog adds several enhancements to Verilog for representing large amounts of data. The Verilog array construct is extended both in how data can be represented and for operations on arrays. Structure and union types have been added to Verilog as a means to represent collections of variables.

This section presents:

• Structures

• Unions

• Operations on structures and unions

• Unpacked arrays

• Packed arrays

• Operations on arrays

• Array foreach loop

• Special system functions for working with arrays

• The $bits “sizeof” system function

5.1 Structures

Design data often has logical groups of signals, such as all the control signals for a bus protocol, or all the signals used within a state controller. The Verilog language does not have a convenient mechanism for collecting common signals into a group. Instead, designers must use ad-hoc grouping methods such as naming conventions where each signal in a group starts or ends with a common set of characters.

structures are defined using a C-like syntax

SystemVerilog adds C-like structures to Verilog. A structure is a convenient way of grouping several pieces of related information together. A structure is declared using the struct keyword. Structure members can be any variable type, including user-defined types, and any constant type. An example structure declaration is:

struct {
	int a, b; // 32-bit variables
	opcode_t opcode; // user-defined type
	logic [23:0] address; // 24-bit variable
	bit error; // 1-bit 2-state var.
} Instruction_Word;

the C “tag” is not allowed

The structure declaration syntax in SystemVerilog is very similar to the C language. The one difference is that C allows for an optional “tag” after the struct keyword and before the opening brace. SystemVerilog does not allow a tag.

structures are a collection of variables and/or constants

A structure is a collection of variables and/or constants under a single name. The entire collection can be referenced, using the name of the structure. Each member within the structure also has a name, which is used to select it from the structure. A structure member is referenced the same as in C.

<structure_name>.<variable_name>

For example, to assign a value to the opcode member of the preceding structure, the reference is:

Instruction_Word.address = 32’hF000001E;

structures are different than arrays

A structure differs from an array, in that an array is a collection of elements that are all the same type and size, whereas a structure is a collection of variables and/or constants that can be different types and sizes. Another difference is that the elements of an array are referenced by an index into the array, whereas the members of a structure are referenced by a member name.

5.1.1 Structure declarations

Variables or nets can be defined as a structure

structures can be variables or nets

A structure is a collection of variables, which can be accessed separately or as a whole. A structure as a whole can be declared as a variable using the var keyword. A structure can also be defined as a net, using any of the Verilog net types, such as wire or tri. When defined as a net type, all members of the structure must be 4-state types.

var struct { // structure variable
logic [31:0] a, b;
logic [ 7:0] opcode;
logic [23:0] address;
} Instruction_Word_var;

wire struct { // structure net
logic [31:0] a, b;
logic [ 7:0] opcode;
logic [23:0] address;
} Instruction_Word_net;

Declaring a structure as a var or net type is optional. If not specified, then the structure as a whole is considered to be a variable.

struct { // structure variable
logic [31:0] a, b;
logic [ 7:0] opcode;
logic [23:0] address;
} Instruction_Word_var;

Note that, though a structure as a whole can be declared as a net type, net types cannot be used within structures. Nets can be grouped together under a single name using SystemVerilog interfaces, which are discussed in Chapter 10.

Typed and anonymous structures

structures can be user-defined types

User-defined types can be created from structures, using the typedef keyword, as discussed in section 4.1 on page 75. Declaring a structure as a user-defined type does not allocate any storage. Before values can be stored in the members of a structure that is defined as a user-defined type, a variable of that user-defined type must be declared.

typedef struct { // structure definition
logic [31:0] a, b;
logic [ 7:0] opcode;
logic [23:0] address;
} instruction_word_t;

instruction_word_t IW; // structure allocation

When a structure is declared without using typedef, it is referred to as an anonymous structure.

struct {
logic [31:0] a, b;
logic [ 7:0] opcode;
logic [23:0] address;
} instruction;

Local and shared structure definitions

structure definitions can be shared using packages or $unit

A typed structure can be defined within a module or interface, allowing its use throughout that design block. If a typed structure definition needs to be used in more than one design block, or as a port of a module or interface, then the structure definition should be placed in a package, and imported into design blocks or the $unit compilation-unit space. Typed structures can also be defined directly in the $unit compilation-unit space. Definitions in packages and in $unit are discussed in section 2.1 on page 8 and section 2.2 on page 14 in Chapter 2.

5.1.2 Assigning values to structures

Initializing structures

structures can be initialized using a list of values

The members of a structure can be initialized at the time the structure is instantiated, using a set of values enclosed between the tokens ’{ and }. The number of values between the braces must exactly match the number of members in the structure.

typedef struct {
logic [31:0] a, b;
logic [ 7:0] opcode;
logic [23:0] address;
} instruction_word_t;
instruction_word_t IW = ’{100, 3, 8’hFF, 0};

A similar syntax is used for defining structure constants or structure parameters.

NOTE: The SystemVerilog value list syntax is not the same as C.

SystemVerilog uses the tokens ’{ } to enclose a value list, whereas C uses { }. Early versions of the SystemVerilog draft standard used simple{ } braces to delimit value lists, like C. The final version of the IEEE SystemVerilog changed the delimiter to ’{ } to distinguish the list of values from Verilog’s { } concatenation operator.

Assigning to structure members

three ways to assign to structures

A value can be assigned to any member of a structure by referencing the name of the member.

typedef struct {
logic [31:0] a, b;
logic [ 7:0] opcode;
logic [23:0] address;
} instr_t;

instr_t IW;

always @(posedge clock, negedge resetN)
	if (!resetN) begin
		IW.a = 100; // reference structure member
		IW.b = 5;
		IW.opcode = 8’hFF;
		IW.address = 0;
	end
	else begin
		...
	end

Assigning structure expressions to structures

a structure expression is enclosed within ’{ ... }

A complete structure can be assigned a structure expression. A structure expression is formed using a comma-separated list of values enclosed between the tokens ’{ and }, just as when initializing a structure. The braces must contain a value for each member of the structure.

always @(posedge clock, negedge resetN)
	if (!resetN) IW = ’{100, 5, 8’hFF, 0};
	else begin
		...
	end

a structure expression can be listed by order or by member name

The values in the structure expression can be listed in the order in which they are defined in the structure. Alternatively, the structure expression can specify the names of the structure members to which values are being assigned, where the member name and the value are separated by a colon. When member names are specified, the expression list can be in any order.

IW = ’{address:0, opcode:8’hFF, a:100, b:5};

It is illegal to mix listing by name and listing by order in the same structure expression.

IW = ’{address:0, 8’hFF, 100, 5}; // ERROR

Default values in structure expressions

some or all members of a structure can be assigned a default value

A structure expression can specify a value for multiple members of a structure by specifying a default value. The default value can be specified for all members of a structure, using the default keyword.

IW = ’{default:0}; // set all members of IW to 0

The default value can also be specified just for members of a specific type within the structure, using the keyword for the type. The default keyword or type keyword is separated from the value by a colon.

typedef struct {
	real r0, r1;
	int i0, i1;
	logic [ 7:0] opcode;
	logic [23:0] address;
} instruction_word_t;

instruction_word_t IW;

always @(posedge clock, negedge resetN)
	if (!resetN)
	IW = ’{ real:1.0, default:0 };
	// assign all real members a default of 1.0
	// and all other members a default of 0
	else begin
		...
	end

The default value assigned to structure members must be compatible with the type of the member. Compatible values are ones that can be cast to the member’s type.

default value precedence

There is a precedence in how structure members are assigned values. The default keyword has the lowest precedence, and will be overridden by any type-specific defaults. Type-specific default values will be overridden by any explicitly named member values. The following structure expression will assign r0 a value of 1.0, r1 a value of 3.1415, and all other members of the structure a value of 0.

typedef struct {
	real r0, r1;
	int i0, i1;
	logic [ 7:0] opcode;
	logic [23:0] address;
} instruction_word_t;

instruction_word_t IW;

IW = ’{ real:1.0, default:0, r1:3.1415 };

5.1.3 Packed and unpacked structures

unpacked structures can have padding

By default, a structure is unpacked. This means the members of the structure are treated as independent variables or constants that are grouped together under a common name. SystemVerilog does not specify how software tools should store the members of an unpacked structure. The layout of the storage can vary from one software tool to another.

packed structures are stored without padding

A structure can be explicitly declared as a packed structure, using the packed keyword. A packed structure stores all members of the structure as contiguous bits, in a specified order. A packed structure is stored as a vector, with the first member of the structure being the left-most field of the vector. The right-most bit of the last member in the structure is the least-significant bit of the vector, and is numbered as bit 0. This is illustrated in Figure 5-1.

struct packed {
	logic valid;
	logic [ 7:0] tag;
	logic [31:0] data;
} data_word;

Figure 5-1: Packed structures are stored as a vector

The members of a packed structure can be referenced by either the name of the member or by using a part select of the vector represented by the structure. The following two assignments will both assign to the tag member of the data_word structure:

data_word.tag = 8’hf0;
data_word[39:32] = 8’hf0; // same bits as tag

NOTE: Packed structures can only contain integral values.

packed structures must contain packed variables

All members of a packed structure must be integral values. An integral value is a value that can be represented as a vector, such as byte, int and vectors created using bit or logic types. A structure cannot be packed if any of the members of the structure cannot be represented as a vector. This means a packed structure cannot contain real or shortreal variables, unpacked structures, unpacked unions, or unpacked arrays.

Operations on packed structures

packed structures are seen as vectors

Because a packed structure is stored as a vector, operations on the complete structure are treated as vector operations. Therefore, math operations, logical operations, and any other operation that can be performed on vectors can also be performed on packed structures.

typedef struct packed {
	logic valid;
	logic [ 7:0] tag;
	logic [31:0] data;
} data_word_t;

data_word_t packet_in, packet_out;

always @(posedge clock)
	packet_out <= packet_in << 2;

Note that when a packed structure is assigned a list of values between the tokens ’{ and }, as discussed in section 5.1.2 on page 98, values in the list are assigned to members of the structure. The packed structure is treated the same as an unpacked structure in this circumstance, rather than as a vector. The values within the ’{ } braces are separate values for each structure member, and not a concatenation of values.

packet_in = ’{1, ’1, 1024};

The preceding line assigns 1 to valid, FF (hex) to tag, and 1024 (decimal) to data.

Signed packed structures

a packed structures used as a vector can be signed or unsigned

Packed structures can be declared with the signed or unsigned keywords. These modifiers affect how the entire structure is perceived when used as a vector in mathematical or relational operations. They do not affect how members of the structure are perceived. Each member of the structure is considered signed or unsigned, based on the type declaration of that member. A partselect of a packed structure is always unsigned, the same as part selects of vectors in Verilog.

typedef struct packed signed {
	logic valid;
	logic [ 7:0] tag;
	logic signed [31:0] data;
} data_word_t;

data_word_t A, B;

always @(posedge clock)
	if ( A < B ) // signed comparison
		...

5.1.4 Passing structures through ports

ports can be declared as a structure type

Structures can be passed through module and interface ports. The structure must first be defined as a user-defined type using typedef, which then allows the module or interface port to be declared as the structure type.

package definitions;
	typedef enum {ADD, SUB, MULT, DIV} opcode_t;

	typedef struct {
		logic [31:0] a, b;
		opcode_t opcode;
		logic [23:0] address;
		logic error;
	} instruction_word_t;
endpackage

module alu
(input definitions::instruction_word_t IW,
 input wire clock);
...
endmodule

An alternative style to explicitly naming the package containing the typedef definition as part of the module port would be to import the package into the $unit compilation-unit declaration space. It is also possible to directly define the user-defined types in the $unit space. Importing packages and using the $unit compilation-unit space are discussed in Chapter 2.

When an unpacked structure is passed through a module port, a structure of the exact same type must be connected on each side of the port. Anonymous structures declared in two different modules, even if they have the exact same name, members and member names, are not the same type of structure. Passing unpacked structures through module ports is discussed in more detail in section 9.6.2 on page 252.

5.1.5 Passing structures as arguments to tasks and functions

structures can be passed to tasks and functions

Structures can be passed as arguments to a task or function. To do so, the structure must be defined as a user-defined type using typedef, so that the task or function argument can then be declared as the structure type.

module processor (...);
...
typedef enum {ADD, SUB, MULT, DIV} opcode_t;
typedef struct { // typedef is local
	logic [31:0] a, b;
	opcode_t opcode;
	logic [23:0] address;
	logic error;
} instruction_word_t;

function alu (input instruction_word_t IW);
	...
endfunction

endmodule

When a task or function is called that has an unpacked structure as a formal argument, a structure of the exact same type must be passed to the task or function. An anonymous structure, even if it has the exact same members and member names, is not the same type of structure.

5.1.6 Synthesis guidelines

Both unpacked and packed structures are synthesizable. Synthesis supports passing structures through module ports, and in/out of tasks and functions. Assigning values to structures by member name and as a list of values is supported.

5.2 Unions

a union only stores a single value

SystemVerilog adds C-like unions to Verilog. A union is a single storage element that can have multiple representations. Each representation of the storage can be a different type.

The declaration syntax for a union is similar to a structure, and members of a union are referenced in the same way as structures.

union {
	int i;
	int unsigned u;
} data;
...
data.i = -5;
$display("data is %d", data.i);
data.u = -5;
$display("now data is %d", data.u);

unions reduce storage and may improve performance

Although the declaration syntax is similar, a union is very different than a structure. A structure can store several values. It is a collection of variables under a single name. A union can only store one value. A typical application of unions is when a value might be represented as several different types, but only as one type at any specific moment in time.

Typed and anonymous unions

A union can be defined as a type using typedef, in the same way as structures. A union that is defined as a user-defined type is referred to as a typed union. If typedef is not used, the union is referred to as an anonymous union.

typedef union { // typed union
	int i;
	int unsigned u;
} data_t;

data_t a, b; // two variables of type data_t

5.2.1 Unpacked unions

An unpacked union can contain any variable type, including real types, unpacked structures and unpacked arrays. Software tools can store values in unpacked unions in an arbitrary manner. There is no requirement that each tool align the storage of the different types used within the union in the same way.

Unpacked unions are not synthesizable. They are an abstract type which are useful for high-level system and transaction level models. As such, it may be useful to store any type in the union including 4-state types, 2-state types, and non-synthesizable types such as real types.

NOTE: Reading from an unpacked union member that is different than the last member written may cause indeterminate results.

If a value is stored using one union member, and read back from a different union member, then the value that is read is not defined, and may yield different results in different software tools.

The following example is not synthesizable, but shows how an unpacked union can store very different value types. The example shows a union that can store a value as either an int type or a real type. Since these types are stored very differently, it is important that a value always be read back from the union in the same type with which it is written. Therefore, the example contains extra logic to track how values were stored in the union. The union is a member of a structure. A second member of the structure is a flag that can be set to indicate that a real value has been stored in the union. When a value is read from the union, the flag can be checked to determine what type the union is storing.

struct {
	bit is_real;
	union {
		int i;
		real r;
	} value;
} data;
//...
always @(posedge write) begin
	case (operation_type)
		INT_OP: begin
			data.value.i <= 5;
			data.is_real <= 0;
		end
		FP_OP: begin
			data.value.r <= 3.1415;
			data.is_real <= 1;
		end
	endcase
end
//...
always @(posedge read) begin
    if (data.is_real)
		real_operand <= data.value.r;
	else
		int_operand <= data.value.i;
end

5.2.2 Tagged unions

A union can be declared as tagged.

union tagged {
	int i;
	real r;
} data;

tagged unions contain an implicit tag member

A tagged union contains an implicit member that stores a tag, which represents the name of the last union member into which a value was stored. When a value is stored in a tagged union using a tagged expression, the implicit tag automatically stores information as to which member the value was written.

values are stored in tagged unions using a tagged expression

A value can be written into a tagged union member using a tagged expression. A tagged expression has the keyword tagged followed by the member name, followed by a value to be stored. A tagged expression is assigned to the name of the union. For example:

data = tagged i 5; // store the value 5 in data.i, and set the implicit tag

Values are read from the tagged union using the union member name.

d_out = data.i; // read value from union

tagged unions check that the union is used in a consistent way

Tagged unions require that software tools monitor the usage of the union, and generate an error message if a value is read from a different union member than the member into which a tagged expression value was last written. For example, if the last tagged expression to write to the union specified member i (an int type), then the following line of code will result in a run-time error:

d_out = data.r; // ERROR: member does not match the union’s implicit tag

Once a value has been assigned to a tagged union using a tagged expression, values can be written to the same union member using the member name. If the member name specified does not match the current union tag, a run-time error will result.

data.i = 7; // write to union member; member name must match the current union tag

It is still the designer’s responsibility to ensure that the design consistently reads values from the union member in which data was last stored. If, however, a design flaw should use the union in an inconsistent way, software tools must inform the designer of the error.

5.2.3 Packed unions

packed union members all have the same size

A union can be declared as packed in the same way as a structure. In a packed union, the number of bits of each union member must be the same. This ensures that a packed union will represent its storage with the same number of bits, regardless of member in which a value is stored. Because of this restrictions, packed unions are syntheiszable.

A packed union can only store integral values, which are values made up of 1 or more contiguous bits. If any member of a packed union is a 4-state type, then the union is 4-state. A packed union cannot contain real or short-real variables, unpacked structures, unpacked unions, or unpacked arrays.

A packed union allows data to be written using one format and read back using a different format. The design model does not need to do any special processing to keep track of how data was stored. This is because the data in a packed union will always be stored using the same number of bits.

The following example defines a packed union in which a value can be represented in two ways: either as a data packet (using a packed structure) or as an array of bytes.

typedef struct packed {
	logic [15:0] source_address;
	logic [15:0] destination_address;
	logic [23:0] data;
	logic [ 7:0] opcode;
} data_packet_t;

union packed {
	data_packet_t packet; // packed structure
	logic [7:0][7:0] bytes; // packed array
} dreg;

Figure 5-2: Packed union with two representations of the same storage

Because the union is packed, the information will be stored using the same bit alignment, regardless of which union representation is used. This means a value could be loaded using the array of bytes format (perhaps from a serial input stream of bytes), and then the same value can be read using the data_packet format.

always @(posedge clock, negedge resetN)
if (!resetN) begin
	dreg.packet <= ’0; // store as packet type
	i <= 0;
end
else if (load_data) begin
	dreg.bytes[i] <= byte_in; // store as bytes
	i <= i + 1;
end
always @(posedge clock)
	if (data_ready)
		case (dreg.packet.opcode) // read as packet
		...

Packed, tagged unions

A union can be declared as both packed and tagged. In this case, the union members can be different bit sizes, but must still be only integral types (1 or more contiguous bits). Packed, tagged unions only permit reading from the same union member that matches the member of the last tagged expression written into the union.

union tagged packed {
	logic [15:0] short_word;
	logic [31:0] word;
	logic [63:0] long_word;
} data_word;

5.2.4 Synthesis guidelines

NOTE: Only packed unions are synthesizable

packed unions can be synthesized

A union only stores a single value, regardless of how many type representations are in the union. To realize the storage of a union in hardware, all members of the union must be stored as the same vector size using the same bit alignment. Packed unions represent the storage of a union in this way, and are synthesizable. An unpacked union does not guarantee that each type will be stored in the same way, and is therefore not synthesizable.

Packed, tagged unions are intended to be synthesizable, but at the time this book was written, were not widely supported by synthesis compilers.

5.2.5 An example of using structures and unions

Structures provide a mechanism to group related data together under a common name. Each piece of data can be referenced individually by name, or the entire group can be referenced as a whole. Unions allow one piece of storage to be used in multiple ways.

The following example models a simple Arithmetic Logic Unit that can operate on either signed or unsigned values. The ALU opcode, the two operands, and a flag to indicate if the operation data is signed or unsigned, are passed into the ALU as a single instruction word, represented as a structure. The ALU can operate on either signed values or unsigned values, but not both at the same time. Therefore the signed and unsigned values are modeled as a union of two types. This allows one variable to represent both signed and unsigned values.

Chapter 11 presents another example of using structures and unions to represent complex information in a simple and intuitive form.

Example 5-1: Using structures and unions

package definitions;
	typedef enum {ADD, SUB, MULT, DIV, SL, SR} opcode_t;
	typedef enum {UNSIGNED, SIGNED} operand_type_t;
	typedef union packed {
	logic [31:0] u_data;
	logic signed [31:0] s_data;
} data_t;

typedef struct packed {
	opcode_t opc;
	operand_type_t op_type;
	data_t op_a;
	data_t op_b;
} instr_t;
endpackage

import definitions::*; // import package into $unit space

module alu
(input instr_t IW,
output data_t alu_out);
always @(IW) begin
	if (IW.op_type == SIGNED) begin
		case (IW.opc)
				ADD : alu_out.s_data = IW.op_a.s_data + IW.op_b.s_data;
				SUB : alu_out.s_data = IW.op_a.s_data - IW.op_b.s_data;
				MULT: alu_out.s_data = IW.op_a.s_data * IW.op_b.s_data;
				DIV : alu_out.s_data = IW.op_a.s_data / IW.op_b.s_data;
				SL : alu_out.s_data = IW.op_a.s_data <<< 2;
				SR : alu_out.s_data = IW.op_a.s_data >>> 2;
			endcase
		end
		else begin
			case (IW.opc)
				ADD : alu_out.u_data = IW.op_a.u_data + IW.op_b.u_data;
				SUB : alu_out.u_data = IW.op_a.u_data - IW.op_b.u_data;
				MULT: alu_out.u_data = IW.op_a.u_data * IW.op_b.u_data;
				DIV : alu_out.u_data = IW.op_a.u_data / IW.op_b.u_data;
				SL : alu_out.u_data = IW.op_a.u_data << 2;
				SR : alu_out.u_data = IW.op_a.u_data >> 2;
			endcase
		end
	end
endmodule

5.3 Arrays

5.3.1 Unpacked arrays

Verilog-1995 arrays

The basic syntax of a Verilog array declaration is:

<data_type> <vector_size> <array_name> <array_dimensions>

For example:

reg [15:0] RAM [0:4095]; // memory array

Verilog-1995 only permitted one-dimensional arrays. A one-dimensional array is often referred to as a memory, since its primary purpose is to model the storage of hardware memory devices such as RAMs and ROMs. Verilog-1995 also limited array declarations to just the variable types reg, integer and time.

Verilog arrays

Verilog-2001 significantly enhanced Verilog-1995 arrays by allowing any variable or net type except the event type to be declared as an array, and by allowing multi-dimensional arrays. Beginning with Verilog-2001, both variable types and net types can be used in arrays.

// a 1-dimensional unpacked array of 1024 1-bit nets
wire n [0:1023];
// a 1-dimensional unpacked array of 256 8-bit variables 
reg [7:0] LUT [0:255];
// a 1-dimensional unpacked array of 1024 real variables
real r [0:1023];
// a 3-dimensional unpacked array of 32-bit int variables
integer i [7:0][3:0][7:0];

Verilog restricts array access to one element at a time

Verilog restricts the access to arrays to just one element of the array at a time, or a bit-select or part-select of a single element. Any reading or writing to multiple elements of an array is an error.

integer i [7:0][3:0][7:0];
integer j;
j = i[3][0][1]; // legal: selects 1 element
j = i[3][0]; // illegal: selects 8 elements

unpacked arrays store each element independently

SystemVerilog refers to the Verilog style of array declarations as unpacked arrays. With unpacked arrays, each element of the array may be stored independently from other elements, but grouped under a common array name. Verilog does not define how software tools should store the elements in the array. For example, given an array of 8-bit wide elements, a simulator or other software tool might store each 8-bit element in 32-bit words. Figure 5-3 illustrates how the following declaration might be stored within memory.

wire [7:0] table [3:0];

Figure 5-3: Unpacked arrays can store each element independently

SystemVerilog enhancements to unpacked arrays

SystemVerilog allows unpacked arrays of any type

SystemVerilog extends unpacked array dimensions to include the Verilog event type, and the SystemVerilog types: logic, bit, byte, int, longint, shortreal, and real. Unpacked arrays of user-defined types defined using typedef can also be declared, including types using struct and enum.

bit [63:0] d_array [1:128]; // array of vectors
shortreal cosines [0:89]; // array of floats
typedef enum {Mo, Tu, We, Th, Fr, Sa, Su} Week;
Week Year [1:52]; // array of Week types

SystemVerilog can reference all or slices of an array

SystemVerilog also adds to Verilog the ability to reference an entire unpacked array, or a slice of multiple elements within an unpacked array. A slice is one or more contiguously numbered elements within one dimension of an array. These enhancements make it possible to copy the contents of an entire array, or a specific dimension of an array into another array.

NOTE: The left-hand and right-hand sides of an unpacked array copy must have identical layouts and types.

copying into multiple elements of an unpacked array

In order to directly copy multiple elements into an unpacked array, the layout and element type of the array or array slice on the lefthand side of the assignment must exactly match the layout and element type of the right-hand side. That is, the element type and size and the number of dimensions copied must be the same.

The following examples are legal. Even though the array dimensions are not numbered the same, the size and layout of each array is the same.

int a1 [7:0][1023:0]; // unpacked array
int a2 [1:8][1:1024]; // unpacked array
a2 = a1; // copy an entire array
a2[3] = a1[0]; // copy a slice of an array

Array copying is discussed in more detail later in this chapter, in section 5.3.7 on page 124.

Simplified unpacked array declarations

C arrays are specified by size

C language arrays always begin with address 0. Therefore, an array declaration in C only requires that the size of the array be specified. For example:

int array [20]; // a C array with addresses from 0 to 19

Hardware addressing does not always begin with address 0. Therefore, Verilog requires that array declarations specify a starting address and an ending address of an array dimension.

int array [64:83]; // a Verilog array with addresses from 64 to 83

SystemVerilog unpacked arrays can also be specified by size

SystemVerilog adds C-like array declarations to Verilog, allowing unpacked arrays to be specified with a dimension size, instead of starting and ending addresses. The array declaration:

logic [31:0] data [1024];

is equivalent to the declaration:

logic [31:0] data [0:1023];

As in C, the unpacked array elements are numbered, starting with address 0 and ending with address size-1.

The simplified C-style array declarations cannot be used with vector declarations (packed arrays). The following example is a syntax error.

logic [32] d; // illegal vector declaration

5.3.2 Packed arrays

The Verilog language allows vectors to be created out of single-bit types, such as reg and wire. The vector range comes before the signal name, whereas an unpacked array range comes after the signal name.

Verilog vectors are onedimensional packed arrays

SystemVerilog refers to vector declarations as packed arrays. A Verilog vector is a one-dimensional packed array.

wire [3:0] select; // 4-bit "packed array"
reg [63:0] data; // 64-bit "packed array"

SystemVerilog allows multidimensional packed arrays

SystemVerilog adds the ability to declare multiple dimensions in a packed array.

logic [3:0][7:0] data; // 2-D packed array

packed arrays have no padding

SystemVerilog defines how the elements of a packed array are stored. The entire array must be stored as contiguous bits, which is the same as a vector. Each dimension of a packed array is a sub field within the vector.

In the packed array declaration above, there is an array of 4 8-bit sub-arrays. Figure 5-4 illustrates how the two-dimensional array above will be stored, regardless of the software compiler, operating system or platform.

Figure 5-4: Packed arrays are stored as contiguous elements

logic [3:0][7:0] data; // 2-D packed array

Packed array types

NOTE: Only bit-wise types can be packed.

Packed arrays must be formed using bit-wise types (logic, bit or reg), other packed arrays, packed structures, and packed unions. Packed arrays can also be formed from any of the Verilog net data types (wire, uwire, wand, tri, triand, trior, tri0, tri1 or trireg).

typedef struct packed {
	logic [ 7:0] crc;
	logic [63:0] data;
} data_word;

data_word [7:0] darray; // 1-D packed array of packed structures

Referencing packed arrays

A packed array can be referenced as a whole, as bit-selects, or as part-selects. Multidimensional packed arrays can also be referenced in slices. A slice is one or more contiguous dimensions of an array.

logic [3:0][7:0] data; // 2-D packed array

wire [31:0] out = data; // whole array

wire sign = data[3][7]; // bit-select

wire [3:0] nib = data [0][3:0]; // part-select

byte high_byte;
assign high_byte = data[3]; // 8-bit slice

logic [15:0] word;
assign word = data[1:0]; // 2 slices

Operations on packed arrays

any vector operation can be performed on packed arrays

Because packed arrays are stored as vectors, any legal operation that can be performed on a Verilog vector can also be performed on packed arrays. This includes being able to do bit-selects and partselects from the packed array, concatenation operations, math operations, relational operations, bit-wise operations, and logical operations.

logic [3:0][15:0] a, b, result; // packed arrays
...
result = (a << 1) + b;

packed arrays use Verilog vector rules

There is no semantic difference between a Verilog vector and a SystemVerilog packed array. Packed arrays use the standard Verilog vector rules for operations and assignment statements. When there is a mismatch in vector sizes, a packed array will be truncated on the left or extended to the left, just as with a Verilog vector.

5.3.3 Using packed and unpacked arrays

The ability to declare multi-dimensional arrays as either packed arrays or unpacked arrays gives a great deal of flexibility on how to represent large amounts of complex data. Some general guidelines on when to use each type of array follow.

use unpacked arrays to model memories, and with abstract types

Use unpacked arrays to model:

• Arrays of byte, int, integer, real, unpacked structures, unpacked unions, and other types that are not bit-wise types

• Arrays where typically one element at a time is accessed, such as with RAMs and ROMs

module ROM (...);
	byte mem [0:4095]; // unpacked array of bytes
	assign data = select? mem[address]: ’z;
	...

use packed arrays to create vectors with sub-fields

Use packed arrays to model:

• Vectors made up of 1-bit types (the same as in Verilog)

• Vectors where it is useful to access sub-fields of the vector

logic [39:0][15:0] packet; // 40 16-bit words

packet = input_stream; // assign to all words

data = packet[24]; // select 1 16-bit word

tag = packet[3][7:0]; // select part of 1 word

5.3.4 Initializing arrays at declaration

Packed array initialization

packed arrays are initialized the same as with vectors

Packed arrays can be initialized at declaration using a simple assignment, like vectors in Verilog. The assignment can be a constant value, a concatenation of constant values or a replication of constant values.

logic [3:0][7:0] a = 32’h0; // vector assignment
logic [3:0][7:0] b = {16’hz,16’h0}; // concatenate operator
logic [3:0][7:0] c = {16{2’b01}}; // replicate operator

In the examples above, the { } braces represent the Verilog concatenate operator.

Unpacked array initialization

unpacked arrays are initialized with a list of values

Unpacked arrays can be initialized at declaration, using a list of values enclosed between ’{ and } braces for each array dimension. This syntax is similar to assigning a list of values to an array in C, but with the added apostrophe before the opening brace. Using ’{ as the opening delimiter shows that enclosed values are a list of expressions, not the Verilog concatenation of expressions. Note that the C shortcut of omitting the inner braces is not allowed in SystemVerilog. The assignment requires nested sets of braces that exactly match the dimensions of the array.

int d [0:1][0:3] = ’{ ’{7,3,0,5}, ’{2,0,1,6} };
// d[0][0] = 7
// d[0][1] = 3
// d[0][2] = 0
// d[0][3] = 5
// d[1][0] = 2
// d[1][1] = 0
// d[1][2] = 1
// d[1][3] = 6

SystemVerilog provides a shortcut for declaring a list of values. An inner list for one dimension of an array can be repeated any number of times using a Verilog-like replicate factor. The replicate factor is not followed by an apostrophe.

int e [0:1][0:3] = ’{ 2{7,3,0,5} };
// e[0][0] = 7
// e[0][1] = 3
// e[0][2] = 0
// e[0][3] = 5
// e[1][0] = 7
// e[1][1] = 3
// e[1][2] = 0
// e[1][3] = 5

NOTE: The ’{ } list and ’{n{ }} replicated list operators are not the same as the Verilog { } concatenate and {n{ }} replicate operators.

the { } braces are used two ways

When initializing an unpacked array, the ’{ } braces represent a list of values. This is not the same as a Verilog concatenate operation. As a list of values, each value is assigned to its corresponding element, following the same rules as Verilog assignment statements. This means unsized literal values can be specified in the list, as well as real values.

The Verilog concatenation and replication operators use the { } braces, without the leading apostrophe. These operators require that literal values have a size specified, in order to create the resultant single vector. Unsized numbers and real values are not allowed in concatenation and replication operators.

Specifying a default value for unpacked arrays

an array can be initialized to a default value

SystemVerilog provides a mechanism to initialize all the elements of an unpacked array, or a slice of an unpacked array, by specifying a default value. The default value is specified within ’{ } braces using the default keyword, which is separated from the value by a colon. The value assigned to the array must be compatible with the type of the array. A value is compatible if it can be cast to that type.

int a1 [0:7][0:1023] = ’{default:8’h55};

An unpacked array can also be an array of structures or other userdefined types (see section 5.3.11 on page 128). These constructs can contain multiple types. To allow initializing different types within an array to different values, the default value can also be specified using the keyword for the type instead of the default keyword. A default assignment to the array will automatically descend into structures or unions to find variables of the specified type. Refer to section 5.1.2 on page 98, for an example of specifying default values based on types.

5.3.5 Assigning values to arrays

Assigning values to unpacked arrays

The Verilog language supports two ways to assign values to unpacked arrays:

• A single element can be assigned a value.

• A bit-select or part select of a single element can be assigned a value (added as part of the Verilog-2001 standard).

SystemVerilog extends Verilog with two additional ways to assign values to unpacked arrays:

• The entire array can be assigned a list of values.

• A slice of the array can be assigned a list of values.

The list of values is specified between ’{ } braces, the same as with initializing unpacked arrays, as discussed in section 5.3.4 on page 119.

byte a [0:3][0:3];
a[1][0] = 8’h5; // assign to one element
a = ’{’{0,1,2,3},
	’{4,5,6,7},
	’{7,6,5,4},
	’{3,2,1,0}};
// assign a list of values to the full array
a[3] = ’{’hF, ’hA, ’hC, ’hE};
// assign list of values to slice of the array

The list of assignments to an unpacked array can also specify a default assignment, using the default keyword. As procedural assignments, specific portions of an array can be set to different default values.

always @(posedge clock, negedge resetN)
	if (!resetN) begin
		a = ’{default:0}; // init entire array
		a[0] = ’{default:4}; // init slice of array
	end
	else begin
		//...
	end

Assigning values to packed arrays

multi-dimensional packed arrays are vectors with sub-fields

Packed arrays are vectors (that might happen to have sub-fields), and can be assigned values, just as with Verilog vectors. A packed array can be assigned a value:

• To one element of the array

• To the entire array (vector)

• To a part select of the array

• To a slice (multiple contiguous sub-fields) of the array

logic [1:0][1:0][7:0] a; // 3-D packed array
a[1][1][0] = 1’b0; // assign to one bit
a = 32’hF1A3C5E7; // assign to full array
a[1][0][3:0] = 4’hF; // assign to a part select
a[0] = 16’hFACE; // assign to a slice
a = {16’bz, 16’b0}; // assign concatenation

5.3.6 Copying arrays

This subsection describes the rules for the four possible combinations of assigning arrays to arrays.

Assigning packed arrays to packed arrays

assigning packed array to packed array is allowed

A packed array can be assigned to another packed array. Since packed arrays are treated as vectors, the arrays can be of different sizes and types. Standard Verilog assignment rules for vectors are used to truncate or extend the arrays if there is a mismatch in array sizes.

bit [1:0][15:0] a; // 32 bit 2-state vector
logic [3:0][ 7:0] b; // 32 bit 4-state vector
logic [15:0] c; // 16 bit 4-state vector
logic [39:0] d; // 40 bit 4-state vector
b = a; // assign 32-bit array to 32-bit array
c = a; // upper 16 bits will be truncated
d = a; // upper 8 bits will be zero filled

Assigning unpacked arrays to unpacked arrays

assigning unpacked array to unpacked array is allowed

Unpacked arrays can be directly assigned to unpacked arrays only if both arrays have exactly the same number of dimensions and element sizes, and are of the same types. The assignment is done by copying each element of one array to its corresponding element in the destination array. The array elements in the two arrays do not need to be numbered the same. It is the layout of the arrays and the types that must match exactly.

logic [31:0] a [2:0][9:0];
logic [0:31] b [1:3][1:10];
a = b; // assign unpacked array to unpacked array

assigning unpacked arrays of different sizes requires casting

If the two unpacked arrays are not identical in layout, the assignment can still be made using a bit-stream cast operation. Bit-stream casting is presented later in this chapter, in section 5.3.7 on page 124.

Assigning unpacked arrays to packed arrays

assigning unpacked arrays to packed arrays requires casting

An unpacked array cannot be directly assigned to a packed array. This is because in the unpacked array, each element is stored independently and therefore cannot be treated as an integral expression (a vector). However unpacked arrays can be assigned to packed arrays using bit-stream casting, as discussed in section 5.3.7 on page 124.

Assigning packed arrays to unpacked arrays

assigning packed arrays to unpacked arrays requires casting

A packed array cannot be directly assigned to an unpacked array. Even if the dimensions of the two arrays are identical, the packed array is treated as a vector, which cannot be directly assigned to an unpacked array, where each array element can be stored independent from other elements. However, the assignment can be made using a bit-stream cast operation.

5.3.7 Copying arrays and structures using bit-stream casting

a bit-stream cast converts arrays to a temporary vector of bits

A bit-stream cast temporarily converts an unpacked array to a stream of bits in vector form. The identity of separate elements within the array is lost—the temporary vector is simply a stream of bits. This temporary vector can then be assigned to another array, which can be either a packed array or an unpacked array. The total number of bits represented by the source and destination arrays must be the same. However, the size of each element in the two arrays can be different.

Bit-stream casting provides a mechanism for:
• assigning an unpacked array to an unpacked array of a different layout

• assigning an unpacked array to a packed array

• assigning a packed array to an unpacked array

• assigning a structure to a packed or unpacked array

• assigning a fixed or dynamically sized array to a dynamically sized array

• assigning a structure to another structure with a different layout.

Bit-stream casting uses the SystemVerilog static cast operator. The casting requires that at least the destination array be represented as a user-defined type, using typedef.

typedef int data_t [3:0][7:0]; // unpacked type
data_t a; // unpacked array
int b [1:0][3:0][3:0]; // unpacked array
a = data_t’(b); // assign unpacked array to unpacked array of a different layout

The cast operation is performed by converting the source array (or structure) into a temporary vector representation (a stream of bits) and then assigning groups of bits to each element of the destination
array. The assignment is made from left to right, such that the leftmost bits of the source bit-stream are assigned to the first element of the destination array, the next left-most bits to the second element, and so forth.

5.3.8 Arrays of arrays

an array can mix packed and unpacked dimensions

It is common to have a combination of unpacked arrays and packed arrays. Indeed, a standard Verilog memory array is actually a mix of array types. The following example declares an unpacked array of 64-bit packed arrays:

logic [63:0] mem [0:4095];

This next example declares an unpacked array of 32-bit elements, where each element is a packed array, divided into 4 8-bit sub fields:

wire [3:0][7:0] data [0:1023];

Indexing arrays of arrays

unpacked dimensions are indexed before packed dimensions

When indexing arrays of arrays, unpacked dimensions are referenced first, from the left-most dimension to the right-most dimension. Packed dimensions (vector fields) are referenced second, from the left-most dimension to the right-most dimension. Figure 5-5 illustrates the order in which dimensions are selected in a mixed packed and unpacked multi-dimensional array.

Figure 5-5: Selection order for mixed packed/unpacked multi-dimensional array

5.3.9 Using user-defined types with arrays

arrays can contain user-defined types

User-defined types can be used as elements of an array. The following example defines a user type for an unsigned integer, and declares an unpacked array of 128 of the unsigned integers.

typedef int unsigned uint;
uint u_array [0:127]; // array of user types

User-defined types can also be defined from an array definition. These user types can then be used in other array definitions, creating a compound array.

typedef logic [3:0] nibble; // packed array
nibble [31:0] big_word; // packed array

The preceding example is equivalent to:

logic [31:0][3:0] big_word;

Another example of a compound array built up from user-defined types is:

typedef logic [3:0] nibble; // packed array
typedef nibble nib_array [0:3]; // unpacked
nib_array compound_array [0:7]; // unpacked

This last example is equivalent to:

logic [3:0] compound_array [0:7][0:3];

5.3.10 Passing arrays through ports and to tasks and functions

In Verilog, a packed array is referred to as a vector, and is limited to a single dimension. Verilog allows packed arrays to be passed through module ports, or to be passed in or out of tasks and functions. Verilog does not allow unpacked arrays to be passed through module ports, tasks or functions.

SystemVerilog allows unpacked arrays as ports and arguments

SystemVerilog extends Verilog by allowing arrays of any type and any number of dimensions to be passed through ports or task/function arguments.

To pass an array through a port, or as an argument to a task or function, the port or task/function formal argument must also be declared as an array. Arrays that are passed through a port follow the same rules and restrictions as arrays that are assigned to other arrays, as discussed in section 5.3.6 on page 123.

module CPU (...);
	...
	logic [7:0] lookup_table [0:255];
	lookup i1 (.LUT(lookup_table));
	...
endmodule

module lookup (output logic [7:0] LUT [0:255]);
	...
	initial load(LUT); //task call
	task load (inout logic [7:0] t [0:255]);
		...
	endtask
endmodule

5.3.11 Arrays of structures and unions

arrays can contain structures or unions

Packed and unpacked arrays can include structures and unions as elements in the array. In a packed array, the structure or union must also be packed.

typedef struct packed { // packed structure
	logic [31:0] a;
	logic [ 7:0] b;
} packet_t;
packet_t [23:0] packet_array; // packed array of 24 structures

typedef struct { // unpacked structure
	int a;
	real b;
} data_t;
data_t data_array [23:0]; // unpacked array of 24 structures

5.3.12 Arrays in structures and unions

structures and unions can contain arrays

Structures and unions can include packed or unpacked arrays. A packed structure or union can only include packed arrays.

struct packed { // packed structure
	logic parity;
	logic [3:0][ 7:0] data; // 2-D packed array
} data_word;

struct { // unpacked structure
	logic data_ready;
	logic [7:0] data [0:3]; // unpacked array
} packet_t;

5.3.13 Synthesis guidelines

Arrays and assignments involving arrays are synthesizable. Specifically:

• Arrays declarations — Both unpacked and packed arrays are synthesizable. The arrays can have any number of dimensions.

• Assigning values to arrays — synthesis supports assigning values to individual elements of an array, bit-selects or part-selects of an array element, array slices, or entire arrays. Assigning lists of literal values to arrays is also synthesizable, including literals using the default keyword.

• Copying arrays — Synthesis supports packed arrays directly assigned to packed arrays. Synthesis also supports unpacked arrays directly assigned to unpacked arrays of the same layout. Assigning any type of array to any type of array using bit-stream casting is also synthesizable.

• Arrays in structures and unions — The use of arrays within structures and unions is synthesizable. Unions must be packed, which means arrays within the union must be packed).

• Arrays of structures or unions — Arrays of structures and arrays of unions are synthesizable (unions must be packed). A structure or union must be typed (using typedef) in order to define an array of the structure or union.

• Passing arrays — Arrays passed through module ports, or as arguments to a task or function, is synthesizable.

5.3.14 An example of using arrays

The following example models an instruction register using a packed array of 32 instructions. Each instruction is a compound value, represented as a packed structure. The operands within an instruction can be signed or unsigned, which are represented as a union of two types. The inputs to this instruction register are the separate operands, opcode, and a flag indicating if the operands are signed or unsigned. The model loads these separate pieces of information into the instruction register. The output of the model is the array of 32 instructions.

Example 5-2: Using arrays of structures to model an instruction register

package definitions;
	typedef enum {ADD, SUB, MULT, DIV, SL, SR} opcode_t;
	typedef enum {UNSIGNED, SIGNED} operand_type_t;
	typedef union packed {
	logic [31:0] u_data;
	logic signed [31:0] s_data;
} data_t;

typedef struct packed {
	opcode_t opc;
	operand_type_t op_type;
	data_t op_a;
	data_t op_b;
} instr_t;
endpackage

import definitions::*; // import package into $unit space

module instruction_register (
	output instr_t [0:31] instr_reg, // packed array of structures
	input data_t operand_a,
	input data_t operand_b,
	input operand_type_t op_type,
	input opcode_t opcode,
	input logic [4:0] write_pointer
);
always @(write_pointer) begin
	instr_reg[write_pointer].op_type = op_type;
	instr_reg[write_pointer].opc = opcode;

	// use op_type to determine the operand type stored in the input operand union
	if (op_type == SIGNED) begin
		instr_reg[write_pointer].op_a.s_data = operand_a.s_data;
		instr_reg[write_pointer].op_b.s_data = operand_b.s_data;
	end
	else begin
		instr_reg[write_pointer].op_a.u_data = operand_a.u_data;
		instr_reg[write_pointer].op_b.u_data = operand_b.u_data;
	end
end
endmodule

5.4 The foreach array looping construct

foreach loops traverse arrays of any number of dimensions

SystemVerilog adds a foreach loop, which can be used to iterate over the elements of single- and multi-dimensional arrays, without having to specify the size of each array dimension. The argument to a foreach loop is the name of an array followed by a comma-separated list of loop variables enclosed in square brackets. Each loop variable corresponds to one of the dimensions of the array.

int sum [1:8] [1:3];
foreach ( sum[i,j] )
sum[i][j] = i + j; // initialize array

The mapping of loop variables to array indexes is determined by the dimension cardinality, as described in section 5.3.8 on page

foreach loop variables are not declatred

It is not necessary to specify a loop variable for each dimension of an array. A dimension can be skipped by showing a variable position using two commas, without a variable name. Empty loop variables indicate that the loop will not iterate over that dimension of the array. Contiguous empty loop variables at the end of the variable list can be omitted without listing the additional commas.

The following example is a function that generates a check bit for each byte in a 128-bit vector. The vector is represented as a two dimensional packed array of 16 8-bit elements. A foreach loop is specified with just one variable, which represents the first dimension (the [15:0] dimension) of the array.

function [15:0] gen_crc (logic [15:0] [7:0] d);
	foreach (gen_crc[i]) gen_crc[i] = ^d[i];
endfunction

Loop variables are automatic, read-only, and local to the loop. The type of each loop variable is implicitly declared to be consistent with the type of array index, which will be int for the types of arrays that have been presented in this book. (SystemVerilog also has associative arrays, which might use a different type for its indices. Associative arrays are not synthesizable).

special system functions for working with arrays

SystemVerilog adds several special system functions for working with arrays. These system functions allow writing verification routines that work with any size array. They may also be useful in abstract models.

$dimensions(array_name)

• Returns the number of dimensions in the array (returns 0 if the object is not an array)

$left(array_name, dimension)

• Returns the most-significant bit (msb) number of the specified dimension. Dimensions begin with the number 1, starting from the left-most unpacked dimension. After the right-most unpacked dimension, the dimension number continues with the left-most packed dimension, and ends with the right-most packed dimension. For the array:

logic [1:2][7:0] word [0:3][4:1];

$left(word,1) will return 0
$left(word,2) will return 4
$left(word,3) will return 1
$left(word,4) will return 7

$right(array_name, dimension)

• Returns the least-significant bit (lsb) number of the specified dimension. Dimensions are numbered the same as with $left.

$low(array_name, dimension)

• Returns the lowest bit number of the specified dimension, which may be either the msb or the lsb. Dimensions are numbered the same as with $left. For the array:

logic [7:0] word [1:4];

$low(word,1) returns 1, and $low(word,2) returns 0.

$high(array_name, dimension)

• Returns the highest bit number of the specified dimension, which may be either the msb or the lsb. Dimensions are numbered the same as with $left.

$size(array_name, dimension)

• Returns the total number of elements in the specified dimension (same as $high - $low + 1). Dimensions are numbered the same as with $left.

$increment(array_name, dimension)

• Returns 1 if $left is greater than or equal to $right, and -1 if $left is less than $right. Dimensions are numbered the same as with $left.

The following code snippet shows how some of these special array system functions can be used to increment through an array, without needing to hard code the size of each array dimension.

logic [3:0][7:0] array [0:1023];
int d = $dimensions(array);
	if (d > 0) begin // object is an array
		for (int j = $right(array,1);
			j != ($left(array,1) + $increment(array,1) );
			j += $increment(array,1))
			begin
				... // do something
			end
end

In this example:

$right(array,1) returns 1023
$left(array,1) returns 0
$increment(array,1) returns -1

for (int j = 1023; j != -1; j += -1)
	begin
		...
	end

The example above could also have been implemented using a foreach loop, as follows:

foreach ( array[j] )
	begin
		...
	end

The foreach loop is discussed earlier in this chapter, in section 5.4 on page 130. When iterating over entire dimensions, and when the total number of loop dimensions is known, the foreach loop may be a simpler and more intuitive style than using the array query functions. The advantage of the array query functions is that they provide more information about how an array is declared, including how many dimensions an array contains. This information can be used to iterate of portions of certain dimensions.

Synthesis guidelines

These array query functions are synthesizable, provided that the array has a fixed size, and the dimension number argument is a constant, or is not specified at all. This is an exception to the general rule that synthesis compilers do not support the usage of system tasks or functions. The foreach loop is also synthesizable, provided the array has a fixed size.

5.6 The $bits “sizeof” system function

$bits is similar to C’s sizeof function

SystemVerilog adds a $bits system function, which returns how many bits are represented by any expression. The expression can contain any type of value, including packed or unpacked arrays, structures, unions, and literal numbers. The syntax of $bits is:

$bits(expression)

Some examples of using $bits are:

bit [63:0] a;
logic [63:0] b;
wire [3:0][7:0] c [0:15];
struct packed {byte tag; logic [31:0] addr;} d;

$bits(a) returns 64

$bits(b) returns 64

$bits(c) returns 512

$bits(d) returns 40

$bits(a+b) returns 128

Synthesis guidelines

The $bits system function is synthesizable, provided the argument to $bits is not a dynamically sized array. The return value of $bits can be determined statically at elaboration time, and is therefore treated as a simple literal value for synthesis.

5.7 Dynamic arrays, associative arrays, sparse arrays and strings

SystemVerilog also adds dynamic array types to Verilog:

• Dynamic arrays

• Associative arrays

• Sparse arrays

• Strings (character arrays)

NOTE: These special array types are not synthesizable.

Dynamically sized arrays are not synthesizable, and are intended for use in verification routines and for modeling at very high levels of abstraction. The focus of this book is on writing models with SystemVerilog that are synthesizable. Therefore, these array types are not covered in the following subsections. More details on these object-oriented array types can be found in the companion book, SystemVerilog for Verification1.

5.8 Summary

SystemVerilog adds the ability to represent complex data sets as single entities. Structures allow variables to be encapsulated into a single object. The structure can be referenced as a whole. Members within the structure can be referenced by name. Structures can be packed, allowing the structure to be manipulated as a single vector. SystemVerilog unions provide a way to model a single piece of storage at an abstract level, where the value stored can be represented as any variable type.

SystemVerilog also extends Verilog arrays in a number of ways. With SystemVerilog, arrays can be assigned values as a whole. All of an array, or slices of one dimension of an array, can be copied to another array. The basic Verilog vector declaration is extended to permit multiple dimensions, in the form of a packed array. A packed array is essentially a vector that can have multiple sub fields. SystemVerilog also provides a number of new array query system functions that are used to determine the characteristics of the array.

Chapter 11 contains a more extensive example of using structures, unions and arrays to represent complex data in a manner that is concise, intuitive and efficient, and yet is fully synthesizable.