Operator overloading

There are various kinds of arithmetic operators that can be useful: saturating, arbitrary size floating point, carry save, etc.

It is convenient to use the normal arithmetic operators for readability, rather than relying on function calls.

overload_declaration ::=

bind overload_operator function data_type function_identifier ( overload_proto_formals ) ;

overload_operator ::= + | ++ | | – – | * | ** | / | % | == | != | < | <= | > | >= | =

overload_proto_formals ::= data_type {, data_type}

The overload declaration allows the arithmetic operators to be applied to data types that are normally illegal for them, such as unpacked structures. It does not change the meaning of the operators for those types where it is legal to apply them. This means that such code does not change behavior when operator overloading is used.

The overload declaration links an operator to a function prototype. The arguments are matched, and the type of the result is then checked. Multiple functions can have the same arguments and different return types. If no expected type exists because the operator is in a self-determined context, then a cast must be used to select the correct function. Similarly, if more than one expected type is possible, due to nested operators, and could match more than one function, a cast must be used to select the correct function. An expected result type exists in any of the following contexts:

  • The right-hand side of an assignment or assignment expression
  • Actual input argument of a task or function call
  • Input port connection of a module, interface, or program
  • Actual parameter to a module, interface, program, or class
  • Relational operator with unambiguous comparison
  • Inside a cast

For example, suppose there is a structure type float:

typedef struct {
bit sign;
bit [3:0] exponent;
bit [10:0] mantissa;
} float;

The + operator can be applied to this structure by invoking a function as indicated in the overloading declarations below:

bind + function float faddif(int, float);
bind + function float faddfi(float, int);
bind + function float faddrf(real, float);
bind + function float faddrf(shortreal, float);
bind + function float faddfr(float, real);
bind + function float faddfr(float, shortreal);
bind + function float faddff(float, float);
bind + function float fcopyf(float); // unary +
bind + function float fcopyi(int); // unary +
bind + function float fcopyr(real); // unary +
bind + function float fcopyr(shortreal); // unary +
float A, B, C, D;
assign A = B + C; //equivalent to A = faddff(B, C);
assign D = A + 1.0; //equivalent to A = faddfr(A, 1.0);

The overloading declaration links the + operator to each function prototype according to the equivalent argument types in the overloaded expression, which normally must match exactly. The exception is if the actual argument is an integral type and there is only one prototype with a corresponding integral argument, the actual is implicitly cast to the type in the prototype.

Note that the function prototype does not need to match the actual function declaration exactly. If it does not, then the normal implicit casting rules apply when calling the function. For example, the fcopyi function can be defined with an int argument:

function float fcopyi (int I);
float o;
o.sign = i[31];
o.exponent = 0;
o.mantissa = 0;

return o;
endfunction

Overloading the assignment operator also serves to overload implicit assignments or casting. Here these are using the same functions as the unary +.

bind = function float fcopyi(int); // cast int to float
bind = function float fcopyr(real); // cast real to float
bind = function float fcopyr(shortreal); // cast shortreal to float

The operators that can be overloaded are the arithmetic operators, the relational operators, and the assignment.

Note that the assignment operator from a float to a float cannot be overloaded here because it is already legal. Similarly, equality and inequality between floats cannot be overloaded. No format can be assumed for 0 or 1, so the user cannot rely on subtraction to give equality or on addition to give increments. Similarly, no format can be assumed for positive or negative, so comparison must be explicitly coded.

An assignment operator such as += is automatically built from both the + and = operators successively, where the = has its normal meaning. For example

float A, B;
bind + function float faddff(float, float);
always @(posedge clock) A += B; // equivalent to A = A + B

The scope and visibility of the overload declaration follow the same search rules as a data declaration. The overload declaration must be defined before use in a scope that is visible. The function bound by the overload declaration uses the same scope search rules as a function enabled from the scope where the operator is invoked.

<< Previous | Next >>

Tagged union expressions

A tagged union expression (packed or unpacked) is expressed using the keyword tagged followed by a tagged union member identifier, followed by an expression representing the corresponding member value. For void members, the member value expression is omitted. For Example:

typedef union tagged {
void Invalid;
int Valid;
} VInt;
VInt vi1, vi2;
vi1 = tagged Valid (23+34); // Create Valid int
vi2 = tagged Invalid; // Create an Invalid value

In the tagged union expressions below, the expressions in braces are structure expressions.

typedef union tagged {
struct {
bit [4:0] reg1, reg2, regd;
} Add;
union tagged {
bit [9:0] JmpU;
struct {
bit [1:0] cc;
bit [9:0] addr;
} JmpC;
} Jmp;
} Instr;
Instr i1, i2; // Create an Add instruction with its 3 register fields
i1 = ( e ? tagged Add { e1, 4, ed }; // struct members by position
: tagged Add { reg2:e2, regd:3, reg1:19 }); // by name (order irrelevant)
// Create a Jump instruction, with "unconditional" sub-opcode
i1 = tagged Jmp (tagged JmpU 239);
// Create a Jump instruction, with "conditional" sub-opcode
i2 = tagged Jmp (tagged JmpC { 2, 83 }); // inner struct by position
i2 = tagged Jmp (tagged JmpC { cc:2, addr:83 }); // by name

The type of a tagged union expression must be known from its context (e.g., it is used in the right-hand side of an assignment to a variable whose type is known, or it is has a cast, or it is used inside another expression from which its type is known). The expression evaluates to a tagged union value of that type. The tagged union expression can be completely type-checked statically: the only member names allowed after the tagged keyword are the member names for the expression type, and the member expression must have the corresponding member type.

An uninitialized variable of tagged union type shall be undefined. This includes the tag bits. A variable of tagged union type can be initialized with a tagged union expression provided the member value expression is a legal initializer for the member type.

Members of tagged unions can be read or assigned using the usual dot notation. Such accesses are completely type-checked, i.e., the value read or assigned must be consistent with the current tag. In general, this can require a runtime check. An attempt to read or assign a value whose type is inconsistent with the tag results in a runtime error.

All the following examples are legal only if the instruction variable instr currently has the tag Add:

x = i1.Add.reg1;
i1.Add = {19, 4, 3};
i1.Add.reg2 = 4;

<< Previous | Next >>

Structure expressions

A structure expression (packed or unpacked) can be built from member expressions using braces and commas, with the members in declaration order. Replicate operators can be used to set the values for the exact number of members. Each member expression shall be evaluated in the context of an assignment to the type of the corresponding member in the structure. It can also be built with the names of the members.

module mod1;
typedef struct {
int x;
int y;
} st;
st s1;
int k = 1;
initial begin
#1 s1 = {1, 2+k}; // by position
#1 $display( s1.x, s1.y);
#1 s1 = {x:2, y:3+k); // by name
#1 $display( s1);
#1 $finish;
end
endmodule

It can sometimes be useful to set structure members to a value without having to keep track of how many members there are, or what the names are. This can be done with the default keyword:

initial s1 = {default:2}; // sets x and y to 2

The {member:value} or {data_type: default_value} syntax can also be used:

ab abkey[1:0] = {{a:1, b:1.0}, {int:2, shortreal:2.0}};

Note that the default keyword applies to members in nested structures or elements in unpacked arrays in structures. It descends the nesting to a built-in type or a packed array of them.

struct {
int A;
struct {
int B, C;
} BC1, BC2;
}
ABC = {A:1, BC1:{B:2, C:3}, BC2:{B:4,C:5}};
DEF = {default:10};

To deal with the problem of members of different types, a type can be used as the key. This overrides the
default for members of that type:

typedef struct {
logic [7:0] a;
bit b;
bit signed [31:0] c;
string s;
} sa;
sa s2;
initial s2 = {int:1, default:0, string:""}; // set all to 0 except the array of bits to 1 and string to ""

Similarly, an individual member can be set to override the general default and the type default:

initial #10 s1 = {default:’1, s : ""}; // set all to 1 except s to ""

SystemVerilog determines the context of the braces when used in the context of an assignment. If used in the context of an assignment to an unpacked structure, the braces represent an unpacked structure literal or expression. Outside the context of an assignment to an aggregate type, an explicit cast must be used with the braces to distinguish it from a concatenation. When the braces include a label, type, or default key, the braces shall not be interpreted as a concatenation for both packed and unpacked structure types.

The matching rules are as follows:

  • A member:value: specifies an explicit value for a named member of the structure. The named member must be at the top level of the structure—a member with the same name in some level of substructure shall not be set. The value must be castable to the member type and is evaluated in the context of an assignment to the named member, otherwise, an error is generated.
  • The type:value specifies an explicit value for a field in the structure that is equivalent to the type and has not been set by a field name key above. If the same type key is mentioned more than once, the last value is used. The value is evaluated in the context of an assignment to the matching type.
  • The default:value applies to members that are not matched by either member name or type key and are not either structures or unpacked arrays. The value is evaluated in the context of each assignment to a member by default and must be castable to the member type, otherwise an error is generated. For unmatched structure members, the type and default specifiers are applied recursively according to the rules in this section to each member of the substructure. For unmatched unpacked array members, the type and default keys are applied to the array according to the rules for unpacked arrays. Every member must be covered by one of these rules. If the type key, default key, or replication operator is used on an expression with side effects, the number of times that expression evaluates is undefined.

<< Previous | Next >>

Concatenation

Braces ( { } ) are used to show concatenation. The concatenation is treated as a packed vector of bits. It can be used on the left-hand side of an assignment or in an expression.

logic log1, log2, log3;
{log1, log2, log3} = 3’b111;
{log1, log2, log3} = {1’b1, 1’b1, 1’b1}; // same effect as 3’b111

Software tools can generate a warning if the concatenation width on one side of an assignment is different than the expression on the other side. The following examples can give a warning of size mismatch:

bit [1:0] packet = {32’b1,32’b1}; // right hand side is 64 bits
int i = {1’b1, 1’b1}; //right hand side is 2 bits

SystemVerilog enhances the concatenation operation to allow concatenation of variables of type string. In general, if any of the operands is of type string, the concatenation is treated as a string and all other arguments are implicitly converted to the string type. String concatenation is not allowed on the left-hand side of an assignment, only as an expression.

string hello = "hello";
string s;
s = { hello, " ", "world" };
$display( "%s\n", s ); // displays 'hello world'
s = { s, " and goodbye" };
$display( "%s\n", s ); // displays 'hello world and goodbye'

The replication operator (also called a multiple concatenation) form of braces can also be used with variables of type string. In the case of string replication, a non-constant multiplier is allowed.

int n = 3;
string s = {n { "hello " }};
$display( "%s\n", s ); // displays 'hello hello hello '

Unlike bit concatenation, the result of a string concatenation or replication is not truncated. Instead, the destination variable (of type string) is resized to accommodate the resulting string.

Unpacked array expressions

Braces are also used for expressions to assign to unpacked arrays. Unlike in C, the expressions must match element for element, and the braces must match the array dimensions. Each expression item shall be evaluated in the context of an assignment to the type of the corresponding element in the array. This means that the following examples do not give size warnings, unlike the similar assignments above:

bit abc [1:0] = {1,1}; // no size warning as bit can be set to 1
int xyz [1:0] = {1'b1, 1'b1}; // no size warning as int can be set to 1'b1

The syntax of multiple concatenations can be used for unpacked array expressions as well. Each replication represents a single dimension.

bit [1:0] unpackedbits = {2 {y}} ; // same as {y, y}
int n[1:2][1:3] = {2{{3{y}}}}; // same as {{y,y,y},{y,y,y}}

SystemVerilog determines the context of the braces when used in the context of an assignment. If used in the context of an assignment to an unpacked array, the braces represent an unpacked array literal or expression.

Outside the context of an assignment on the right-hand side, an explicit cast must be used with the braces to distinguish it from a concatenation. An aggregate expression cannot be used as the target of an assignment. The following is considered illegal:

logic [2:0] a [1:0];
logic [2:0] b, c;
always {b,c} = a; // illegal assignment the braces are not determined to be an unpacked array expression

It can sometimes be useful to set array elements to a value without having to keep track of how many members there are. This can be done with the default keyword:

initial unpackedint = {default:2}; // sets elements to 2

For more arrays of structures, it is useful to specify one or more matching type keys, as illustrated under structure expressions, below.

struct {int a; time b;} a_key[1:0];
a_key = {{a:1, b:2ns}, {int:5, time:$time}};

When the braces include a type, or default key, the braces shall not be interpreted as a concatenation for both packed and unpacked array types.

The rules for unpacked array matching are as follows:

  • An index:value, specifies an explicit value for a keyed element index. The value is evaluated in the context of an assignment to the indexed element and shall be castable to its type. It shall be an error to specify the same index more than once in a single array expression.
  • For type:value, if the element or sub-array type of the unpacked array is equivalent to this type, then each element or sub-array shall be set to the value. The value must be castable to the array element or sub-array type. Otherwise, if the unpacked array is multidimensional, then there is a recursive descent into each subarray of the array using the rules in this section and the type and default keys. Otherwise, if the unpacked array is an array of structures, there is a recursive descent into each element of the array using the rules for structure expressions and the type and default keys. If more than one type matches the same element, the last value shall be used.
  • For default:value, this key specifies the default value to use for each element of an unpacked array that has not been covered by the earlier rules in this section. The value is evaluated in the context of each assignment to an element covered by the default and must be castable to the array element type. Every element shall be covered by one of these rules. If the type key, default key, or replication operator is used on an expression with side effects, the number of times that expression evaluates is undefined.

<< Previous | Next >>

Operators and Expressions

Assignment operators

SystemVerilog assignment operator includes the C assignment operators and special bitwise assignment operators: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, <<<=, and >>>=. An assignment operator is semantically equivalent to a blocking assignment, with the exception that any left-hand side index expression is only evaluated once. For example:

a[i]+=2; // same as a[i] = a[i] +2;

In SystemVerilog, an expression can include a blocking assignment, provided it does not have a timing control, Such an assignment must be enclosed in parentheses to avoid common mistakes such as using a=b for a==b, or a|=b for a!=b.

The semantics of such an assignment expression are those of a function that evaluates the right-hand side, casts the right-hand side to the left-hand data type, stacks it, updates the left-hand side, and returns the stacked value. The type returned is the type of the left-hand side data type. If the left-hand side is a concatenation, the type returned shall be an unsigned integral value whose bit length is the sum of the length of its operands.

SystemVerilog includes increment and decrement assignment operators ++i, –i, i++, and i–. These do not need parentheses when used in expressions. These increment and decrement assignment operators behave as blocking assignments.

The ordering of assignment operations relative to any other operation within an expression is undefined. An implementation can warn whenever a variable is both written and read or written within an integral expression or in other contexts where an implementation cannot guarantee the order of evaluation. In the following example:

i = 10;
j = i++ + (i = i - 1);

After execution, the value of j can be 18, 19, or 20 depending upon the relative ordering of the increment and the assignment statements.

Operations on logic and bit types

When a binary operator has one operand of type bit and another of type logic, the result is of type logic. If one operand is of type int and the other of type integer, the result is of type integer.

The operators != and == return an X if either operand contains an X or a Z, as in Verilog-2001. This is converted to a 0 if the result is converted to a type bit.

The unary reduction operators (&, ~&, |, ~|, ^, ~^) can be applied to any integer expression (including packed arrays). The operators shall return a single value of type logic if the packed type is four-valued, and of type bit if the packed type is two-valued.

int i;
bit b = &i;
integer j;
logic c = &j;

Wild equality and wild inequality

SystemVerilog wild-card comparison operators, as described below.

=?=             a =?= b            a equals b,            X, and Z values act as wild cards
!?=             a !?= b             a not equal b,         X, and Z values act as wild cards

The wild equality operator (=?=) and inequality operator (!?=) treat X and Z values in a given bit position as a wildcard. A wildcard bit matches any bit value (0, 1, Z, or X) in the value of the expression being compared against it.

These operators compare operands bit for bit and return a 1-bit self-determined result. If the operands to the wild-card equality/inequality are of unequal bit length, the operands are extended in the same manner as for the case equality/inequality operators. If the relation is true, the operator yields a 1. If the relation is false, it yields a 0.

The three types of equality (and inequality) operators in SystemVerilog behave differently when their operands contain unknown values (X or Z). The == and != operators result in X if any of their operands contains an X or Z. The === and !== check the 4-state explicitly, therefore, X and Z values shall either match or mismatch, never resulting in X. The =?= and !?= operators treat X or Z as wild cards that match any value, thus, they too never result in X.

Real operators

Operands of type shortreal have the same operation restrictions as Verilog real operands. The unary operators ++ and — can have operands of type real and shortreal (the increment or decrement is by 1.0). The assignment operators +=, -=, *=, /= can also have operands of type real and shortreal.

If any operand, except before the ? in the ternary operator, is real, the result is real. Otherwise, if any operand, except before the ? in the ternary operator, is shortreal, the result is shortreal.

Size

The number of bits of an expression is determined by the operands and the context, following the same rules as Verilog. In SystemVerilog, casting can be used to set the size context of an intermediate value.

With Verilog, tools can issue a warning when the left and right-hand sides of an assignment are different sizes. Using the SystemVerilog size casting, these warnings can be prevented.

Sign

The rules for determining the signedness of SystemVerilog expression types shall be the same as those for Verilog. A shortreal converted to an integer by type coercion shall be signed.

<< Previous | Next >>

Type Compatibility

SystemVerilog constructs and operations require a certain level of type compatibility for their operands to be legal. There are four levels of type compatibility: Equivalent, Assignment Compatible, Cast Compatible, and Non-Equivalent.

Equivalent Types

Two data types shall be defined as equivalent data types using the following inductive definition. If the two data types are not defined as equivalent using the following definition, then they shall be defined to be non-equivalent.

  • Any built-in type is equivalent to every other occurrence of itself, in every scope.
  • A simple typedef or type parameter override that renames a built-in or user-defined type is equivalent to that built-in or user-defined type within the scope of the type identifier.
typedef bit node; // ’bit’ and ’node’ are equivalent types
typedef type1 type2; // ’type1’ and ’type2’ are equivalent types
  • An anonymous enum, struct, or union type is equivalent to itself among variables declared within the same declaration statement and no other types.
struct {int A; int B;} AB1, AB2; // AB1, AB2 have equivalent types
struct {int A; int B;} AB3; // AB3 is not type equivalent to AB1
  • A typedef for an enum, unpacked struct, or unpacked union, or a class is equivalent to itself and variables are declared using that type within the scope of the type identifier.
typedef struct {int A; int B;} AB_t;
AB_t AB1; AB_t AB2; // AB1 and AB2 have equivalent types
typedef struct {int A; int B;} otherAB_t;
otherAB_t AB3; // AB3 is not type equivalent to AB1 or AB2
  • Packed arrays, packed structures, and built-in integral types are equivalent if they contain the same number of total bits, are either all 2-state or all 4-state, and are either all signed or all unsigned. Note that if any bit of a packed structure or union is 4-state, the entire structure or union is considered 4-state.
typedef bit signed [7:0] BYTE; // equivalent to the byte type
typedef struct packed signed {bit[3:0] a, b;} uint8; // equivalent to the byte type
  • Unpacked array types are equivalent by having equivalent element types and identical shapes. Shape is defined as the number of dimensions and the number of elements in each dimension, not the actual range of the dimension.
bit [9:0] A[0:5];
bit [1:10] B[6];
typedef bit [10:1] uint10;
uint10 C[6:1]; // A, B and C have equivalent types
typedef int anint[0:0]; // anint is not type equivalent to int
  • Explicitly adding signed or unsigned modifiers to a type that does not change its default signing, does not create a non-equivalent type. Otherwise, the signing must match to have equivalence.
typedef bit unsigned ubit; // type equivalent to bit
  • A typedef for an enum, unpacked struct, or unpacked union, or a class type declared in a package is always equivalent to itself, regardless of the scope where the type is imported.

The scope of a type identifier includes the hierarchical instance scope. This means that each instance with user-defined types declared inside the instance creates a unique type. To have type equivalence among multiple instances of the same module, interface, or program, a type must be declared at higher level in the compilation unit scope than the declaration of the module, interface or program, or imported from a package.
The following example is assumed to be within one compilation unit, although the package declaration need not be in the same unit:

package p1;
typedef struct {int A;} t_1;
endpackage
typedef struct {int A;} t_2;

module sub();
import p1:t_1;
parameter type t_3 = int;
parameter type t_4 = int;
typedef struct {int A;} t_5;
t_1 v1; t_2 v2; t_3 v3; t_4 v4; t_5 v5;
endmodule

module top();
typedef struct {int A;} t_6;
sub #(.t_3(t_6)) s1 ();
sub #(.t_3(t_6)) s2 ();

initial begin
s1.v1 = s2.v1; // legal - both types from package p1
s1.v2 = s2.v2; // legal - both types from $unit
s1.v3 = s2.v3; // legal - both types from top
s1.v4 = s2.v4; // legal - both types are int
s1.v5 = s2.v5; // illegal - types from s1 and s2
end

endmodule

Assignment Compatible

All equivalent types and all non-equivalent types that have implicit casting rules defined between them are assignment-compatible types. For example, all integral types are assignment-compatible. Conversion between assignment-compatible types can involve loss of data by truncation or rounding.
Compatibility can be in one direction only. For example, an enum can be converted to an integral type without a cast, but not the other way around.

Cast Compatible

All assignment-compatible types, plus all non-equivalent types that have defined explicit casting rules are cast-compatible types. For example, an integral type requires a cast to be assigned to an enum.

Type Incompatible

These are all the remaining non-equivalent types that have no defined implicit or explicit casting rules. Class handles and chandles are type incompatible with all other types.

<< Previous | Next >>

Signal Aliasing

The Verilog assign statement is a unidirectional assignment and can incorporate a delay and strength change.

To model a bidirectional short-circuit connection it is necessary to use the alias statement. The members of an alias list are signals whose bits share the same physical nets. The example below implements a byte-order swapping between bus A and bus B.

module byte_swap (inout wire [31:0] A, inout wire [31:0] B);
   alias {A[7:0],A[15:8],A[23:16],A[31:24]} = B;
endmodule

This example strips out the least and most significant bytes from a four-byte bus:

module byte_rip (inout wire [31:0] W, inout wire [7:0] LSB, MSB);
   alias W[7:0] = LSB;
   alias W[31:24] = MSB;
endmodule

The bit overlay rules are the same as those for a packed union with the same member types: each member shall be the same size, and connectivity is independent of the simulation host. The nets connected with an alias statement must be type compatible, that is, they have to be of the same net type. For example, it is illegal to connect a wand net to a wor net with an alias statement. This is a stricter rule than applied to nets joining at ports because the scope of an alias is limited and such connections are more likely to be a design error. Variables and hierarchical references cannot be used in alias statements. Any violation of these rules shall be considered a fatal error.

The same nets can appear in multiple alias statements. The effects are cumulative. The following two examples are equivalent. In either case, low12[11:4] and high12[7:0] share the same wires.

module overlap(inout wire [15:0] bus16, inout wire [11:0] low12, high12);
alias bus16[11:0] = low12;
alias bus16[15:4] = high12;
endmodule
module overlap(inout wire [15:0] bus16, inout wire [11:0] low12, high12);
alias bus16 = {high12, low12[3:0]};
alias high12[7:0] = low12[11:4];
endmodule

To avoid errors in the specification, it is not allowed to specify an alias from an individual signal to itself or to specify a given alias more than once. The following version of the code above would be illegal since the top four and bottom four bits are the same in both statements:

alias bus16 = {high12[11:8], low12};
alias bus16 = {high12, low12[3:0]};

This alternative is also illegal because the bits of bus16 are being aliased to itself:

alias bus16 = {high12, bus16[3:0]} = {bus16[15:12], low12};

Alias statements can appear anywhere in module instance statements, If an identifier that has not been declared as a data type appears in an alias statement, then an implicit net is assumed, following the same rules as implicit nets for a module instance. The following example uses an alias along with the automatic name binding to connect pins on cells from different libraries to create a standard macro:

module lib1_dff(Reset, Clk, Data, Q, Q_Bar);
...
endmodule

module lib2_dff(reset, clock, data, a, qbar);
...
endmodule

module lib3_dff(RST, CLK, D, Q, Q_);
...
endmodule

macromodule my_dff(rst, clk, d, q, q_bar); // wrapper cell
input rst, clk, d;
output q, q_bar;
alias rst = Reset = reset = RST;
alias clk = Clk = clock = CLK;
alias d = data = D;
alias q = Q;
alias Q_ = q_bar = Q_Bar = qbar;
‘LIB_DFF my_dff (.*); // LIB_DFF is any of lib1_dff, lib2_dff or lib3_dff
endmodule

<< Previous | Next >>

Data Declarations

There are several forms of data in SystemVerilog: literals, parameters, constants, variables, nets, and attributes
Constants are literals, genvars parameters, localparams, and specparams.
Variables must be written by procedural statements, and nets must be written by continuous assignments or ports.
SystemVerilog extends the functionality of variables by allowing them to either be written by procedural statements or driven by a single continuous assignment, similar to a wire. Since the keyword reg no longer describes the user’s intent in many cases, the keyword logic is added as a more accurate description that is equivalent to reg.

SystemVerilog requires data to be declared before it is used, apart from implicit nets. The rules for implicit nets are the same as in Verilog-2001.

A variable can be static (storage allocated on instantiation and never de-allocated) or automatic (stack storage allocated on entry to a scope (such as a task, function or block) and de-allocated on exit). SystemVerilog follows Verilog with respect of the static default storage class, with automatic tasks and functions, but allows static to override a default of automatic for a particular variable in such tasks and functions.

Constants

Constants are named data variables that never change. There are three kinds of constants, declared with the keywords localparam, specparam, and const.

localparam byte colon1 = ":" ;
specparam int delay = 10 ; // specparams are used for specify blocks
const logic flag = 1 ;

A parameter or local parameter can only be set to an expression of literals, parameters or local parameters, genvars, enumerated names, or a constant function of these. Hierarchical names are not allowed.
A specparam can also be set to an expression containing one or more specparams.

A static constant declared with the const keyword can only be set to an expression of literals, parameters, local parameters, genvars, enumerated names, a constant function of these, or other constants. The parameters, local parameters or constant functions can have hierarchical names because constants declared with the const keyword are calculated after elaboration. An automatic constant declared with the const keyword can be set to any expression that would be legal without the const keyword.

const logic option = a.b.c ;

A constant expression contains literals and other named constants.
An instance of a class (an object handle) can also be declared with the const keyword.

const class_name object = new(5,3);

This means that the object acts like a variable that cannot be written. The arguments to the new method must be constant expressions. The members of the object can be written (except for those members that are declared const).

SystemVerilog enhancements to parameter and localparam constant declarations. SystemVerilog does not change specparam constants declarations. A const form of constant differs from a localparam constant in that the localparam must be set during elaboration, whereas a const can be set during simulation, such as in an automatic task.

Variables

A variable declaration consists of a data type followed by one or more instances.

shortint s1, s2[0:9];

A variable can be declared with an initializer, for example:

int i = 0;

In Verilog-2001, an initialization value specified as part of the declaration is executed as if the assignment were made from an initial block after the simulation has started. Therefore, the initialization can cause an event on that variable at simulation time zero.

In SystemVerilog, setting the initial value of a static variable as part of the variable declaration (including static class members) shall occur before any initial or always blocks are started, and so does not generate an event. If an event is needed, an initial block should be used to assign the initial values.

Initial values in SystemVerilog are not constrained to simple constants; they can include run-time expressions, including dynamic memory allocation. For example, a static class handle or a mailbox can be created and initialized by calling its new method, or static variables can be initialized to random values by calling the $urandom system task. This requires a special pre-initial pass at run-time.

The default values for SystemVerilog variables are:

4 state integral:   ’x
2 state integral:   ’0
real, shortreal:     0.0
Enumeration:      First value in the enumeration
string:        “” (empty string)
event:         New event
class:       Null

Scope and lifetime

Any data declared outside a module, interface, task, or function, is global in scope (can be used anywhere after its declaration) and has a static lifetime (exists for the whole elaboration and simulation time).

SystemVerilog data declared inside a module or interface but outside a task, process or function is local in scope and static lifetime (exists for the lifetime of the module or interface).

Data declared in an automatic task, function, or block has the lifetime of the call or activation and a local scope.

Data declared in a static task, function, or block defaults to a static lifetime and a local scope.

Verilog-2001 allows tasks and functions to be declared as automatic, making all storage within the task or function automatic. SystemVerilog allows specific data within a static task or function to be explicitly declared as automatic. Data declared as automatic has the lifetime of the call or block, and is initialized on each entry to the call or block. The lifetime of a forkjoin, forkjoin_any, or forkjoin_none block shall encompass the execution of all processes spawned by the block. The lifetime of a scope enclosing any forkjoin block includes the lifetime of the forkjoin block.

SystemVerilog also allows data to be explicitly declared as static. Data declared to be static in an automatic task, function or block has a static lifetime and a scope local to the block.

module msl;
int st0; // static
initial begin
int st1; //static
static int st2; //static
automatic int auto1; //Automatic
end
task automatic t1();
int auto2; //Automatic
static int st3; //static
automatic int auto3; //Automatic
endtask
endmodule

SystemVerilog adds an optional qualifier to specify the default lifetime of all variables declared in a task, function, or block defined within a module, interface, or program. The lifetime qualifier is automatic or static. The default lifetime is static.

program automatic test ;
int i; // not within a procedural block - static
task foo( int a ); // arguments and variables in foo are automatic
$display("%0d",a);
endtask
endmodule

Class methods and declared for loop variables are by default automatic, regardless of the lifetime attribute of the scope in which they are declared.
Note that automatic or dynamic variables cannot be written with nonblocking or continuous assignments.
Automatic variables and dynamic constructs—object handles, dynamic arrays, associative arrays, strings, and event variables—shall be limited to the procedural context.

<< Previous | Next >>

Nets, Regs, and logic

Verilog-2001 states that a net can be written by one or more continuous assignments, primitive outputs, or through module ports. The resultant value of multiple drivers is determined by the resolution function of the net type. A net cannot be procedurally assigned. If a net on one side of a port is driven by a variable on the other side, a continuous assignment is implied. A force statement can override the value of a net. When released, it returns to the resolved value.

Verilog-2001 also states that one or more procedural statements can be written to variables, including procedural continuous assignments. The last write determines the value. A variable cannot be continuously assigned. The force statement overrides the procedural assign statement, which in turn overrides the normal assignments. A variable cannot be written through a port; it must go through an implicit continuous assignment to a net.

In SystemVerilog, all variables can now be written either by one continuous assignment or by one or more procedural statements, including procedural continuous assignments. It shall be an error to have multiple continuous assignments or a mixture of procedural and continuous assignments writing to any term in the expansion of a written longest static prefix of a logic variable. All data types can be written through a port.

SystemVerilog variables can be packed or unpacked aggregates of other types. Multiple assignments made to independent elements of a variable are examined individually. An assignment where the left-hand side contains a slice is treated as a single assignment to the entire slice. It shall be an error to have a packed structure or array-type written with a mixture of procedural and continuous assignments. Thus, an unpacked structure or array can have one element assigned procedurally, and another element assigned continuously. And, each element of a packed structure or array can have a single continuous assignment. For example, assume the following structure declaration:

struct {
bit [7:0] A;
bit [7:0] B;
byte C;
} abc;

The following statements are legal assignments to struct abc:

assign abc.C = sel ? 8’hBE : 8’hEF;
not (abc.A[0],abc.B[0]),
(abc.A[1],abc.B[1]),
(abc.A[2],abc.B[2]),
(abc.A[3],abc.B[3]);
always @(posedge clk) abc.B <= abc.B + 1;

The following additional statements are illegal assignments to struct abc:

// Multiple continuous assignments to abc.C
assign abc.C = sel ? 8’hDE : 8’hED;
// Mixing continuous and procedural assignments to abc.A
always @(posedge clk) abc.A[7:4] <= !abc.B[7:4];

For the preceding rule, a declared variable initialization or a procedural continuous assignment is considered a procedural assignment. A force statement is neither a continuous nor a procedural assignment. A release statement shall not change the variable until there is another procedural assignment or shall schedule a re-evaluation of the continuous assignment driving it. A single force or release statement shall not be applied to a whole or part of a variable that is being assigned by a mixture of continuous and procedural assignments.

A continuous assignment is implied when a variable is connected to an input port declaration. This makes assignments to a variable declared as an input port illegal. A continuous assignment is implied when a variable is connected to the output port of an instance. This makes procedural or continuous assignments to a variable connected to the output port of an instance illegal.

SystemVerilog variables cannot be connected to either side of an inout port. SystemVerilog introduces the concept of shared variables across ports with the ref port type.

The compiler can issue a warning if a continuous assignment could drive strengths other than St0, St1, StX, or HiZ to a variable. In any case, SystemVerilog applies automatic type conversion to the assignment, and the strength is lost.

An assignment as part of the logic declaration is a variable initialization, not a continuous assignment. For example:

wire w = vara & varb; // continuous assignment
logic v = consta & constb; // initial procedural assignment
logic vw; // no initial assignment
assign vw = vara & varb; // continuous assignment to a logic
real circ;
assign circ = 2.0 * PI * R; // continuous assignment to a real

<< Previous | Next >>

Array methods

Array Locator Methods

Locator methods iterate over the array elements, which are then used to evaluate the expression specified by the with clause. The iterator argument optionally specifies the name of the variable used by the with expression to designate the element of the array at each iteration. If it is not specified, the name item is used by default. The scope for the iterator name is the with expression.

The following locator methods are supported (the with clause is mandatory) :

  • find(): returns all the elements satisfying the given expression
  • find_index(): returns the indexes of all the elements satisfying the given expression
  • find_first(): returns the first element satisfying the given expression
  • find_first_index(): returns the index of the first element satisfying the given expression
  • find_last(): returns the last element satisfying the given expression
  • find_last_index(): returns the index of the last element satisfying the given expression

For the following locator methods, the with clause (and its expression) can be omitted if the relational operators (<, >, ==) are defined for the element type of the given array. If a with clause is specified, the relational operators (<, >, ==) must be defined for the type of the expression.

  • min(): returns the element with the minimum value or whose expression evaluates to a minimum
  • max(): returns the element with the maximum value or whose expression evaluates to a maximum
  • unique(): returns all elements with unique values or whose expression is unique
  • unique_index(): returns the indexes of all elements with unique values or whose expression is unique

Examples:

string SA[10], qs[$];
int IA[*], qi[$];
// Find all items greater than 5
qi = IA.find( x ) with ( x > 5 );
// Find indexes of all items equal to 3
qi = IA.find_index with ( item == 3 );
// Find the first item equal to Bob
qs = SA.find_first with ( item == "Bob" );
// Find the last item equal to Henry
qs = SA.find_last( y ) with ( y == "Henry" );
// Find the index of the last item greater than Z
qi = SA.find_last_index( s ) with ( s > "Z" );
// Find the smallest item
qi = IA.min;
// Find the string with a largest numerical value
qs = SA.max with ( item.atoi );
// Find all unique strings elements
qs = SA.unique;
// Find all unique strings in lower-case
qs = SA.unique( s ) with ( s.tolower );

Array ordering methods

Array ordering methods can reorder the elements of one-dimensional arrays or queues.
The general prototype for the ordering methods is:

function void ordering_method ( array_type iterator = item )

The following ordering methods are supported:

  • reverse(): reverses all the elements of the array (packed or unpacked). Specifying a with clause shall be a compiler error.
  • sort(): sorts the unpacked array in ascending order, optionally using the expression in the with clause.

The with clause (and its expression) is optional when the relational operators are defined for the array element type.

  • rsort(): sorts the unpacked array in descending order, optionally using the expression in the with clause.

The with clause (and its expression) is optional when the relational operators are defined for the array element type.

  • shuffle(): randomizes the order of the elements in the array. Specifying a with clause shall be a compiler error.

Examples:

string s[] = { "hello", "good", "morning" };
s.reverse; // s becomes { "morning", "good", "hello" };
logic [4:1] a = 4’bXZ01;
a.reverse; // a becomes 4’b10ZX
int q[$] = { 4, 5, 3, 1 };
q.sort; // q becomes { 1, 3, 4, 5 }
struct { byte red, green, blue } c [512];
c.sort with ( item.red ); // sort c using the red field only
c.sort( x ) with ( x.blue << 8 + x.green ); // sort by blue then green

Array reduction methods

Array reduction methods can be applied to any unpacked array to reduce the array to a single value. The expression within the optional with clause can be used to specify the item to use in the reduction.

The prototype for these methods is:

function expression_or_array_type reduction_method (array_type iterator = item)

The method returns a single value of the same type as the array element type or, if specified, the type of the expression in the with clause. The with clause can be omitted if the corresponding arithmetic or boolean reduction operation is defined for the array element type. If a with clause is specified, the corresponding arithmetic or boolean reduction operation must be defined for the type of the expression.

The following reduction methods are supported:

  • sum(): returns the sum of all the array elements, or if a with clause is specified, returns the sum of the values yielded by evaluating the expression for each array element.
  • product(): returns the product of all the array elements, or if a with clause is specified, returns the product of the values yielded by evaluating the expression for each array element.
  • and(): returns the bit-wise AND ( & ) of all the array elements, or if a with clause is specified, returns the bit-wise AND of the values yielded by evaluating the expression for each array element
  • or(): returns the bit-wise OR ( | ) of all the array elements, or if a with clause is specified, returns the bitwise OR of the values yielded by evaluating the expression for each array element
  • xor(): returns the logical XOR ( ^ ) of all the array elements, or if a with clause is specified, returns the XOR of the values yielded by evaluating the expression for each array element.

Examples:

byte b[] = { 1, 2, 3, 4 };
int y;
y = b.sum ; // y becomes 10 => 1 + 2 + 3 + 4
y = b.product ; // y becomes 24 => 1 * 2 * 3 * 4
y = b.xor with ( item + 4 ); // y becomes 12 => 5 ^ 6 ^ 7 ^ 8

Iterator index querying

The expressions used by array manipulation methods sometimes need the actual array indexes at each iteration, not just the array element. The index method of an iterator returns the index value of the specified dimension.

The prototype of the index method is:

function int_or_index_type index ( int dimension = 1 )

The slowest variation is dimension 1. Successively faster varying dimensions have sequentially higher dimension numbers. If the dimension is not specified, the first dimension is used by default. The return type of the index method is an int for all array iterator items except associative arrays, which returns an index of the same type as the associative index type.

For example:

int arr[]
int mem[9:0][9:0], mem2[9:0][9:0];
int q[$];
//Finds all items equal to their position (index)
q = arr.find with ( item == item.index );
//Finds all items in mem that are greater than the corresponding item in mem2
q = mem.find( x ) with ( x > mem2[x.index(1)][x.index(2)] );

<< Previous | Next >>