1. Introduction
to System Verilog
System
Verilog is a standard set of extensions to the IEEE 1364-
2005 Verilog Standard (commonly referred to as “Verilog-2005”).These
extensions integrate many of the features of the SUPERLOG and C languages.
System Verilog also contains many extensions for the verification of large
designs, integrating features from the SUPERLOG, VERA C, C++, and VHDL
languages, along with OVA and PSL assertions. This integrated whole created by
System Verilog greatly exceeds the sum of its individual components, creating a
new type of engineering language, a Hardware Description and Verification
Language or HDVL. Using a single, unified language enables
engineers to model large, complex designs, and verify that these designs are
functionally correct.
The
following list highlights some of the more significant enhancements System Verilog
adds to the Verilog HDL for the design and verification of hardware: This list
is not intended to be all inclusive of every enhancement to Verilog that is in
System Verilog. This list just highlights a few key features that aid in
writing synthesizable hardware models.
·
Interfaces to encapsulate communication
and protocol checking within a design
·
C like data types, such as int
·
User-defined types, using typedef
·
Enumerated types
·
Type casting
·
Structures and unions
·
Packages for definitions shared by
multiple design blocks
·
External compilation-unit scope
declarations
·
++, --, += and other assignment operators
·
Explicit procedural blocks
·
Priority and unique decision modifiers
·
Programming statement enhancements
·
Pass by reference to tasks, functions
and modules
A
data type is a set of values and a set of operations that can be performed on
those values. Data types can be used to declare data objects or to define
user-defined data types that are constructed from other data types. A data
object is a named entity that has a data value and a data type associated with
it, such as a parameter, a variable, or a net.
The
System Verilog value set consists of the following four basic values:
Ø 0—represents
a logic zero, or a false condition
Ø 1—represents
a logic one, or a true condition
Ø x—represents
an unknown logic value
Ø z—represents
a high-impedance state
The
values 0 and 1 are logical complements of one another. When the z value is
present at the input of a gate or when it is encountered in an expression, the
effect is usually the same as an x value.
Several
System Verilog data types are 4-state types, which can store all four logic
values. All bits of 4-state vectors can be independently set to one of the four
basic values. Some System Verilog data types are 2-state, and only store 0 or 1
values in each bit of a vector. System Verilog 2-state data types can
simulate faster, take less memory, and are preferred in some design styles.
Then a 4-state value is automatically converted to a 2-state value, X and Z
will be converted to zeros.
Table
1: Different
system verilog data types
A
singular variable or expression represents a single value, symbol, or handle.
Aggregate expressions and variables represent a set or collection of singular
values. An aggregate type shall be any unpacked structure, unpacked union, or
unpacked array data type.
There
are two main groups of data objects: variables and nets. These two groups
differ in the way in which they are assigned and hold values.
The net types can represent
physical connections between structural entities, such as gates. A net shall
not store a value. Instead, its value shall be determined by the values of its
drivers, such as a continuous assignment or a gate. If no driver is connected
to a net, its value shall be high-impedance (z).
A net
declaration begins with a net type that determines how the values of the nets
in the declaration are resolved. The declaration can include optional
information such as delay values, drive or charge strength, and a data type.
Example1:
Declaration of nets
typedef
logic [31:0]
addressT;
wireaddressT
w1;
wire
struct packed { logic ecc; logic [7:0] data; }
memsig;
If a
data type is not specified in the net declaration or if only a range and/or
signing isspecified, then the data type of the net is implicitly declared as logic.
Example2:
Declaration of nets
wirew; //
equivalent to "wire logic w;"
wire[15:0]
ww; // equivalent to "wire logic [15:0] ww;"
A net
can be written by one or more continuous assignments, by primitive outputs, or
through module ports. 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 variable
is an abstraction of a data storage element. A variable shall store a value
from one assignment to the next. An assignment statement in a procedure acts as
a trigger that changes the value in the data storage element.
One
form of variable declaration consists of a data type followed by one or more
instances.
shortints1,
s2[0:9];
Another form of variable declaration begins
with the keyword var. The data type is optional in this case. If a data
type is not specified or if only a range and/or signing is specified, then the
data type is implicitly declared as logic.
Example3:
Declaration of variables
var
byte my_byte;
// equivalent to "byte my_byte;"
varv; //
equivalent to "var logic v;"
var[15:0]
vw; // equivalent to "var logic [15:0] vw;"
var
enum bit {
clear, error } status;
input
var logic data_in;
var
reg r;
Variables can be written by one or more
procedural statements, including procedural continuous assignments. The last
write determines the value. Alternatively, variables can be written by one
continuous assignment or one port.
Variables can be packed or
unpacked aggregates of different types Multiple assignments made to independent
elements of a variable are examined individually. Independent elements include
different members of a structure, or different elements of an array. Each bit
in a packed type is also an independent element. Thus, in an aggregate of
packed types, each bit in the aggregate is an independent element.
An assignment where the
left-hand side contains a slice is treated as a single assignment to the entire
slice. Thus, a structure or array can have one element assigned procedurally
and another element assigned continuously.
Example4:
assignment to nets and variables
struct{
bit[7:0]
A;
bit[7:0]
B;
byteC;
}
abc;
The
following statements are legal assignments to struct abc:
assignabc.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
assignabc.C
= sel ?8'hDE : 8'hED;
//
Mixing continuous and procedural assignments to abc.A[3]
always@(posedge
clk) abc.A[7:3] <= !abc.B[7:3];
Variable cannot be connected
to inout port.
Variables can be shared across ports with the ref port type. Nets and
variables can be assigned negative values, but only signed types shall retain
the significance of the sign. The byte, shortint, int, integer,
and longint types are signed types by default. Other net and variable
types can be explicitly declared as signed.
A
data object declared as reg, logic, or bit (or as a
matching user-defined type or implicitly as logic) without a range
specification shall be considered 1 bit wide and is known as a scalar. A
multibit data object of one of these types shall be declared by specifying a
range, and is known as a vector.
The range specification gives addresses to the individual bits in
a multibit reg, logic, or bit vector. The most significant
bit specified by the msb constant expression is the left-hand value in
the range, and the least significant bit specified by the lsb constant
expression is the right-hand value in the range.
Both the msb constant expression and the lsb constant expression
shall be constant integer expressions. The msb and lsb constant expressions may
be any integer value—positive, negative, or zero. It shall be illegal for them
to contain any unknown (x) or high-impedance bits. The lsb value may be greater
than, equal to, or less than the msb value.
Vectors shall obey laws of arithmetic modulo-2 to the power n (2n),
where n is the number of bits in the vector. Vectors of reg, logic,
and bit types shall be treated as unsigned quantities, unless
declared to be signed or connected to a port that is declared to be signed
Example5: Declaration
of vectors
logica; //
a scalar variable
logic[3:0]
v; // a 4-bit vector made up of (from MSB to LSB)v[3],
v[2], v[1], and v[0]
logic
signed [3:0]
signed_reg; // a 4-bit vector in range -8 to 7
logic[-1:4]
b; // a 6-bit vector
wirew1,
w2; // declares two nets
logic[4:0]
x, y, z; // declares three 5-bit variables
In the absence of an explicit declaration, an implicit net of
default net type shall be assumed in the following circumstances:
Ø If an identifier is used in a port expression declaration, then an
implicit net of default net type shall be assumed, with the vector width of the
port expression declaration.
Ø If an identifier is used in the terminal list of a primitive
instance or a module instance, and that identifier has not been declared
previously in the scope where the instantiation appears or in any scope whose
declarations can be directly referenced from the scope where the instantiation
appears, then an implicit scalar net of default net type shall be assumed.
Ø If an identifier appears on the left-hand side of a continuous
assignment statement, and that identifier has not been declared previously in
the scope where the continuous assignment statement appears or in any scope
whose declarations can be directly referenced from the scope where the
continuous assignment statement appears, then an implicit scalar net of default
net type shall be assumed.
The term integral is used throughout this standard to refer
to the data types that can represent a single basic integer data type, packed
array, packed structure, packed union, enum variable, or time variable.
The term simple bit vector type is used throughout this
standard to refer to the data types that can directly represent a
one-dimensional packed array of bits.
The real
data type is the same as a C double. The shortreal data type is the same
as a C float. The realtime declarations shall be treated synonymously
with real declarations and can be used interchangeably.
Types
that can have unknown and high-impedance values are called 4-state types.
These are logic, reg, integer, and time. The other
types do not have unknown values and are called 2-state types, for
example, bit and int.
The
difference between int and integer is that int is a
2-state type and integer is a 4-state type. The 4- state values have
additional bits, which encode the X and Z states. The 2-state data types can
simulate faster, take less memory, and are preferred in some design styles. logicandreg
denote the same type.
Automatic
type conversions from a smaller number of bits to a larger number of bits
involve zero extensions if unsigned or sign extensions if signed. Automatic
type conversions from a larger number of bits to a smaller number of bits
involve truncations of the most significant bits (MSBs). When a 4-state value
is automatically converted to a 2-state value, any unknown or high-impedance
bits shall be converted to zeros.
Example6: Declaration of 2-state bit vectors
bit [msb:lsb] varlable_name
[=inltial_value] ;
•
Sized as specified
•
Defaults to unsigned
bitflag;
bit [15:0] sample, temp = 16' hdeed;
bit[7:0] a = 8'h1; // 8'b0000_0001
bit [7:0] b = 'h1; // 8'b0000_0001
bit[ 7: 0] c = ' 1; // 8'b1111_1111
bit[31:0] signed ref_data = -155;
System
Verilog adds the ability to specify unsized literal single-bit values with a
preceding apostrophe ( ' ), but without the base specifier. All bits of the
unsized value are set to the value specified. Supported unsized literals are '
0, ' 1, ' x, ' z.
2-state-type variable name [=initial value]
;
Where 2-state-type is one of
• byte - 8-bit signed data type
• shortint - 16-bit signed
data type
• int - 32-bit signed data type
• longint - 64-bit signed
data type
Example7: Declaration of 2-state
sized data types
shortint temp = 256;
int sample, ref data = -9876;
longint a, b;
longint unsigned testdata;
2-state-type variable_name [=initial_value] ;
Where 2-state-type is one of
• real- Equivalent to double in C
• shortreal- Equivalent to float in C
• realtime
♦
64-bit real variable for use with $realtime
♦ Can
be used interchangeably with real variables
Example8:Declaration of 2-state sized Real data types
realalha = 100 .3 , cov_result ;
realtimet64 ;
#100 t64 = $realtime;
cov_result = $get_coverage();
if (cov_result == 200.0) ... ;
Variable initialized to ’x if initial_value is not
specified
reg [msb:lsb] variable_name [=initial_value] ;
logic[msb:lsb] variable_name [=initial_value] ;
• regandlogic are synonyms
• Can be used in
continuous assignment (single driver only),unlike Verilog
• Can be used as
outputs of modules
• Defaults to
unsigned
Example9: Declaration of 4-state
bit vector
logic [15:0] sample = '1, ref_data = ' x;
assign sample= rtr_io.cb.dout;
Integer variable name [=initial value];
• 32-bit signed
data type
time variable name [=initial value] ;
• 64-bit
unsigned data type
Example10: Declaration of 4-state sized
data types
integer a = -100, b;
time current_time;
b = -a;
current_time = $time;
i f (current_time >= 100ms) . . . ;
A %t format specifier is available for
displaying time in user-defined units and suffix strings.
$timeformat [ (units_number,precision number ,
suffix_string , minimum_field_width ) ] ;
e.g.
$timeformat (-9, 5, “ns", 5 ) ;
$display ("current time is = %t",
$time ) ; / / o r $realtime
/ / displays for
example current time is = 1650.00000ns
You
may define the $timeformat in an initial block to format all future %t uses.
The result of using logical or relational operators on real numbers
and real variables is a single-bit scalarvalue. Not all operators can be used
with expressions involving real numbers and real variables.
Real number constants and real variables are also prohibited in
the following cases:
Ø Edge event controls (posedge, negedge, edge)
applied to real variables
Ø Bit-select or part-select references of variables declared as real
Ø Real number index expressions of bit-select or part-select
references of vectors
Real
numbers shall be converted to integers by rounding the real number to the
nearest integer, rather than by truncating it. Implicit conversion shall take
place when a real number is assigned to an integer. If the fractional part of
the real number is exactly 0.5, it shall be rounded away from zero. Implicit
conversion shall also take place when an expression is assigned to a real.
Individual bits that are x or z in the net or the variable shall be treated as
zero upon conversion.
The string
data type is an ordered collection of characters. The length of a string
variable is the number of characters in the collection. Variables of type string
are dynamic as their length may vary during simulation. A single character
of a string variable may be selected for reading or writing by indexing
the variable. A single character of a string variable is of type byte.
A string variable does not represent a string in the same way as a
string literal. String literals behave like packed arrays of a width that is a
multiple of 8 bits. A string literal assigned to a packed array of an integral
variable of a different size is either truncated to the size of the variable or
padded with zeros to the left as necessary. When using the string data
type instead of an integral variable, strings can be of arbitrary length and no
truncation occurs. String literals are implicitly converted to the string type
when assigned to a string type or used in an expression involving string
type operands.
The indices of string variables shall be numbered from 0 to N–1
(where N is the length of the string) so that index 0 corresponds to the
first (leftmost) character of the string and index N–1 corresponds to
the last (rightmost) character of the string. The string variables can take on
the special value “”, which is the empty string. Indexing an empty string
variable shall be an out-of-bounds access.A string variable shall not contain
the special character "\0". Assigning the value 0 to a string character
shall be ignored.
The syntax to declare a string variable is as follows:
String variable_name [=
initial_value];
Where variable_name is a valid identifier and the optional
initial_value can be a string literal, thevalue “” for an empty string, or a
string data type expression. Anempty string has zero length. System Verilog
provides a set of operators that can be used to manipulate combinations of
string variables and string literals. The basic operators defined on the string
data type are:
Ø ==, !
=, compare () and icompare
Ø (),itoa
(), atoi (), atohex (), toupper (), tolower (), etc.
Ø len
(), getc (), putc (), substr ().
A string literal can be assigned to a variable of a string or
an integral data type. When assigning to a variable f integral data type, if
the number of bits of the data object is not equal to the number of characters
in the string literal multiplied by 8, the literal is right justified and
either truncated on the left or zero filled on the left, as necessary.
Example11:
Declaration of strings
parameter
string default_name = "John Smith";
stringmyName = default_name;
bytec = "A"; // assigns to c "A"
bit[10:0] b = "\x41"; // assigns to b ’b000_0100_0001
bit[1:4][7:0] h = "hello" ; // assigns to h
"ello"
A string literal or an expression of string type can be
assigned directly to a variable of string type (astring variable).
Values of integral type can be assigned to a string variable, but require a
cast. When casting an integral value to a string variable, that variable shall
grow or shrink to accommodate the integral value. If the size of the integral
value is not a multiple of 8 bits, then the value shall be zero-filled on the
left so that its size is a multiple of 8 bits.
A string literal assigned to a string variable is converted
according to the following steps:
Ø All "\0" characters in the string literal are ignored
(i.e., removed from the string).
Ø If the result of the first step is an empty string literal, the
string is assigned the empty string.
Ø Otherwise, the string is assigned the remaining characters in the
string literal.
Casting an integral value to a string variable proceeds in the
following steps:
Ø If the size (in bits) of the integral value is not a multiple of
8, the integral value is left extended and filled with zeros until its bit size
is a multiple of 8.
Ø The extended value is then
treated the same as a string literal, where each successive 8 bits represent a
character.
Ø The steps described above for string literal conversion are then
applied to the extended value.
Example12: String assignment
strings0 =
"String literal assign";// sets s0 to "String literal
assign"
strings1 =
"hello\0world"; // sets s1 to "helloworld"
bit[11:0]
b = 12’ha41;
strings2 = string’(b);
// sets s2 to 16’h0a41
typedef
logic [15:0]
r_t;
r_t
r;
integer
i
= 1;
string
b
= "";
string
a
= {"Hi", b};
r =
r_t'(a); // OK
b = string’(r);
// OK
b =
"Hi"; // OK
b =
{5{"Hi"}}; // OK
a =
{i{"Hi"}}; // OK (non-constant replication)
r =
{i{"Hi"}}; // invalid (non-constant replication)
a =
{i{b}}; // OK
a =
{a,b}; // OK
a =
{"Hi",b}; // OK
r =
{"H",""}; // yields "H\0". "" is
converted to 8'b0
b =
{"H",""}; // yields "H". "" is the
empty string
a[0]
= "h"; // OK, same as a[0] = "cough"
a[0]
= b; // invalid, requires a cast
a[1]
= "\0"; // ignored, a is unchanged
The String operators are used in conditional statements and looping
statements:
a) Str1
== Str2 Equality. Checks whether the two string operands are equal.
Result is 1 if they are equal and 0 if they are not. Both operands can be
expressions of string type, or one can be an expression of string type
and the other can be a string literal, which shall be implicitly converted to string
type for the comparison. If both operands are string literals, the operator
is the same equality operator as for integral types.
b)
Str1 != Str2 Inequality. Logical
negation of ==.
c) Str1
< Str2, Str1 <= Str2,Str1> Str2, Str1 >= Str2 Comparison:
Relational operators return 1 if the corresponding condition is true using the
lexicographic ordering of the two strings Str1 and Str2. The comparison uses
the compare string method. Both operands can be expressions of string type,
or one can be an expression of string type and the other can be a string
literal, which shall be implicitly converted to string type for the
comparison. If both operands are string literals, the operator is the same
comparison operator as for integral types.
d) {Str1,Str2,...,Strn}
Concatenation: Each operand can be a string literal or an expression of string
type. If all the operands are string literals the expression shall behave
as a concatenation of integral values; if the result of such a concatenation is
used in an expression involving string types then it shall be implicitly
converted to string type. If at least one operand is an expression of string
type, then any operands that are string literals shall be converted to string
type before the concatenation is performed, and the result of the
concatenation shall be of string type.
e) {multiplier{Str}} Replication: Str can be a string literal
or an expression of string type. multiplier shall be an expression of
integral type, and is not required to be a constant expression. If multiplier
is non-constant or Str is an expression of string type, the result is a
string containing N concatenated copies of Str, where N is
specified by the multiplier. If Str is a literal and the multiplier is
constant, the expression behaves like numeric replication.
f) Str[index] Indexing. Returns a byte, the ASCII code at the
given index. Indices range from0 to N–1, where N is the number of
characters in the string. If given an index outof range, returns 0.
a) Len()
function
int len();
str.len()
returns the length of the string, i.e., the number of characters in the string
(excluding any terminating character). If str is "", then str.len()
returns 0.
b) Putc()
function
void putc(int
i, byte c);
str.putc(i,
c) replaces the ith character in str with the given integral
value.
putc
does not change the size of str: If i < 0 or i >= str.len(), then str is
unchanged. If the second argument to putc is zero, the string is unaffected.
The putc method assignment str.putc(j, x) is semantically equivalent to str[j]
= x.
c) Getc()
function
byte getc(int
i);
str.getc(i)
returns the ASCII code of the ith character in str.
If
i < 0 or i >= str.len(), then str.getc(i) returns 0.
The
getc method assignment x = str.getc(j) is semantically equivalent to x =
str[j].
d) Toupper()
function
string toupper();
str.toupper()
returns a string with characters in str converted to uppercase. str is unchanged.
e)
Tolower()
function string tolower();
str.tolower() returns a string with characters in str converted to
lowercase. str is unchanged.
f)
Compare()
function int compare(string s);
str.compare(s) compares str and s, as in the ANSI C strcmp
function with regard to lexical ordering and return value.
g)
Icompare()
function int icompare(string s);
str.icompare(s) compares str and s, like the ANSI C strcmp
function with regard to lexical ordering and return value, but the comparison
is case insensitive.
h)
Substr()
function string substr(int i, int j);
str.substr(i, j) returns a new string that is a substring formed
by characters in position i through j of str. If i < 0, j < i, or j >=
str.len(), substr() returns " " (the empty string).
i)
Atoi(), atohex(), atooct(), atobin()
function integer atoi();
function integer atohex();
function integer atooct();
function integer atobin();
str.atoi()
returns the integer corresponding to the ASCII decimal representation in str(string).
Forexample:
str = "123";
inti = str.atoi(); // assigns 123 to i.
The
conversion scans all leading digits and underscore characters ( _ ) and stops
as soon as it encounters any other character or the end of the string. It
returns zero if no digits were encountered. It does not parse the full syntax
for integer literals (sign, size, apostrophe, base).
atohex interprets the string as
hexadecimal.
atooct interprets the string as octal.
atobin interprets the string as binary.
j)
Atoreal()
function
real atoreal();
str.atoreal()
returns the real number corresponding to the ASCII decimal representation in
str. The conversion parses for real constants. The scan stops as soon as it
encounters any character that does not conform to this syntax or the end of the
string. It returns zero if no digits were encountered.
k) Itoa()
function
void itoa(integer
i);
str.itoa(i)
stores the ASCII decimal representation of i into str (inverse of atoi).
l)
Hextoa()
function
void hextoa(integer
i);
str.hextoa(i)
stores the ASCII hexadecimal representation of i into str (inverse of atohex).
m) Octtoa()
function
void octtoa(integer
i);
str.octtoa(i)
stores the ASCII octal representation of i into str (inverse of atooct).
n) Bintoa()
function
void bintoa(integer
i);
str.bintoa(i)
stores the ASCII binary representation of i into str (inverse of atobin).
o) Realtoa()
function
void realtoa(real
r);
str.realtoa(r) stores the ASCII real
representation of r into str (inverse of atoreal).
System
Verilog’s data types can be extended with user-defined types using typedef.
A typedef may be used to give a user-defined
name to an existing data type. For example:
typedef int intP;
The named data type can then be used as follows:
int P a, b;
User-defined data type names must be used for complex data types
in casting, which only allows simple data type names, and as type parameter
values when unpacked array types are used. A type parameter may also be used to
declare a type_identifier. The declaration of a user-defined data type
shall precede any reference to its type_identifier. User-defined data
type identifiers have the same scoping rules as data identifiers,
except that hierarchical references to type_identifier shall not be
allowed.
References
to type identifiers defined within an interface through ports are not
considered hierarchical references and are allowed provided they are locally
redefined before being used. Such a typedef is called an interface
based typedef.
Example13: Interface based
typedef
Interface intf_i;
typedef int data_t;
endinterface
module sub(intf_i
p);
typedef p.data_t
my_data_t;
my_data_t
data;// type of 'data' will be int when connected to interface above
endmodule
Sometimes
a user-defined type needs to be declared before the contents of the type have
been defined. Thisis of use with user-defined types derived from the basic data
types: enum, struct, union, and class. Supportfor
this is provided by the following forms for a forward typedef:
typedef enum type_identifier;
typedef struct type_identifier;
typedef union type_identifier;
typedef class type_identifier;
typedeftype_identifier;
The actual data type definition of a forward typedef declaration
shall be resolved within the same local scope. It shall be an error if the type_identifier
does not resolve to a data type. It shall be an error if a basic data type
was specified by the forward type declaration and the actual type definition does
not conform to the specified basic data type. It shall be legal to have a
forward type declaration in the same scope, either before or after the final
type definition. It shall be legal to have multiple forward type declarations
for the same type identifier in the same scope. While incomplete forward types,
type parameters, and types defined by an interface based typedef may resolve to
class types, use of the class scope resolution operator to select a type with
such a prefix shall be restricted to a typedef declaration. It shall be an
error if the prefix does not resolve to a class.
An enumerated type declares a set of integral named constants.
Enumerated data types provide the capability to abstractly declare strongly
typed variables without either a data type or data value(s) and later add the
required data type and value(s) for designs that require more definition.
Enumerated data types also can be easily referenced or displayed using the
enumerated names as opposed to the enumerated values.
In the absence of a data type declaration, the default data type
shall be int. Any other data type used with enumerated types shall
require an explicit data type declaration.
An enumerated type defines a set of named values. In the following
example, light1 and light2 are defined to be variables of the anonymous
(unnamed) enumerated int type that includes the three members: red,
yellow, and green.
enum{red, yellow, green} light1,
light2; // anonymous int type
An enumerated name with x or z assignments assigned to an enum with
no explicit data type or an explicit 2-state declaration shall be a syntax
error.
// Syntax error: IDLE=2’b00, XX=2’bx <ERROR>, S1=2’b01,
S2=2’b10
enum bit [1:0] {IDLE, XX=’x,
S1=2’b01, S2=2’b10} state, next;
An enum declaration of a 4-state type, such as integer,
that includes one or more names with x or z assignments shall be permitted.
// Correct: IDLE=0, XX=’x, S1=1, S2=2
enum integer {IDLE, XX=’x, S1=’b01,
S2=’b10} state, next;
An unassigned enumerated name that follows an enum name
with x or z assignments shall be a syntax error.
// Syntax error: IDLE=0, XX=’x, S1=??, S2=??
enum integer {IDLE, XX=’x, S1, S2} state,
next;
The values can be cast to integer types and increment from an
initial value of 0. This can be overridden.
enum{bronze=3, silver, gold}
medal; // silver=4, gold=5
Both
the enumeration names and their integer values shall be unique. It shall be an
error to set two values to the same name or to set the same value to two names,
regardless of whether the values are set explicitly or by automatic
incrementing.
//
Error: c and d are both assigned 8
enum{a=0,
b=7, c, d=8} alphabet;
If
the first name is not assigned a value, it is given the initial value of 0.
//
a=0, b=7, c=8
enum{a,
b=7, c} alphabet;
Any
enumeration encoding value that is outside the representable range of the enum
base type shall be an error. If the integer value expression is a sized
literal constant, it shall be an error if the size is different from the enum
base type, even if the value is within the represent able range.
//
Correct declaration - bronze and gold are unsized
enum
bit [3:0]
{bronze='h3, silver, gold='h5} medal2;
//
Correct declaration - bronze and gold sizes are redundant
enum
bit [3:0]
{bronze=4'h3, silver, gold=4'h5} medal3;
//
Error in the bronze and gold member declarations
enum
bit [3:0]
{bronze=5'h13, silver, gold=3'h5} medal4;
//
Error in c declaration, requires at least 2 bits
enum
bit [0:0]
{a,b,c} alphabet;
a)
name = C Associates the constant C to
name.
b)
name[N] : Generates N named
constants in the sequence: name0, name1,..., nameN-1. N shall be a
positive integral number. name[N] = C Optionally, a constant can be assigned to
the generated named constants to associate that constant to the first generated
named constant; subsequent generated named constants are associated consecutive
values.
c)
name[N:M] Creates a sequence of named
constants starting with name N and incrementing or decrementing until reaching
named constant name M. N and M shall be nonnegative integral
numbers. name[N:M] = C Optionally, a constant can be assigned to the generated
named constants to associate that constant to the first generated named
constants; subsequent generated named constants are associated consecutive
values.
For example: typedef enum { add=10, sub[5],
jmp[6:8] } E1;
This
example defines the enumerated type E1, which assigns the number 10 to the
enumerated named constant add. It also creates the enumerated named constants
sub0, sub1, sub2, sub3, and sub4 and assigns them the values 11...15,
respectively. Finally, the example creates the enumerated named constants jmp6,
jmp7, and jmp8 and assigns them the values 16 through 18, respectively.
enum{ register[2]
= 1, register[2:4] = 10 } vr;
The
example above declares enumerated variable vr, which creates the enumerated
named constants register0 and register1, which are assigned the values 1 and 2,
respectively. Next, it creates the enumerated named constants register2,
register3, and register4 and assigns them the values 10, 11 and 12.
Enumerated
types are strongly typed; thus, a variable of type enum cannot be
directly assigned a value that lies outside the enumeration set unless an
explicit cast is used or unless the enum variable is a member of a union.
This is a powerful type-checking aid, which prevents users from accidentally
assigning nonexistent values to variables of an enumerated type.
Enumerated
variables are type-checked in assignments, arguments, and relational operators.
Enumerated variables are auto-cast into integral values, but assignment of
arbitrary expressions to an enumerated variable requires an explicit cast.
Example14: Casting for
enumerations
typedef enum {
red, green, blue, yellow, white, black } Colors;
Colors c;
c = green;
c = 1; //
Invalid assignment
if( 1
== c ) // OK. c is auto-cast to integer
In
the example above, the value green is assigned to the variable c of type
Colors. The second assignment is invalid because of the strict typing rules
enforced by enumerated types.
Elements of enumerated type variables can be used in numerical
expressions. The value used in the expression is the numerical value associated
with the enumerated value.
Example15:
Automatic type casting
typedef
enum { red, green, blue, yellow, white, black } Colors;
Colors
col;
integera, b;
a =
blue * 3;
col
= yellow;
b =
col + green;
From the previous declaration, blue has the numerical value 2.
This example assigns a the value of 6 (2*3), and it assigns b a value of 4
(3+1).
An enum variable or identifier used as part of an
expression is automatically cast to the base type of the enum declaration
(either explicitly or using int as the default). A cast shall be
required for an expression that is assigned to an enum variable where
the type of the expression is not equivalent to the enumeration type of the
variable.
Casting to an enum type shall cause a conversion of the
expression to its base type without checking the validity of the value.
typedef
enum {Red, Green, Blue} Colors;
typedef
enum {Mo,Tu,We,Th,Fr,Sa,Su} Week;
Colors
C;
Week
W;
intI;
C =
Colors'(C+1); // C is converted to an integer, then added toone,
then converted back to a Colors type.
C = C + 1; C++;
C+=2; C = I; // Illegal because they would all be assignments of expressions
without a cast.
C =
Colors'(Su); // Legal; puts an out of range value into C
I = C + W; //
Legal; C and W are automatically cast to int one,
then converted back to a Colors type.
a)
First()
The prototype for the first() method is as follows:
function enum first();
The first() method returns the value of the first member of the
enumeration.
b)
Last()
The prototype for the last() method is as follows:
function enum last();
The last() method returns the value of the last member of the
enumeration.
c)
Next()
The prototype for the next() method is as follows:
function enum next( int unsigned N
= 1 );
The next() method returns the Nth next enumeration value (default
is the next one) starting from the current value of the given variable. A wrap
to the start of the enumeration occurs when the end of the enumeration is
reached. If the given value is not a member of the enumeration, the next()
method returns the default initial value for the enumeration.
d)
Prev()
The prototype for the prev() method is as follows:
function enum prev( int unsigned N
= 1 );
The prev() method returns the Nth previous enumeration value
(default is the previous one) starting from the current value of the given
variable. A wrap to the end of the enumeration occurs when the start of the
enumeration is reached. If the given value is not a member of the enumeration,
the prev() method returns the default initial value for the enumeration.
e)
Num()
The prototype for the num() method is as follows:
function int num();
The num() method returns the number of elements in the given
enumeration.
f)
Name()
The prototype for the name() method is as follows:
function string name();
The name() method returns the string representation of the given
enumeration value. If the given value is not a member of the enumeration, the
name() method returns the empty string.
Example16: Usage of
enumerated type functions
typedef enum {
red, green, blue, yellow } Colors;
Colors c =
c.first;
forever begin
$display(
"%s : %d\n", c.name, c );
if( c
== c.last ) break;
c = c.next;
end
Constants are named data objects that never change. System Verilog
provides three elaboration-time constants: parameter, localparam,
and specparam. System Verilog also provides a run-time constant, const.
The parameter, localparam, and specparam constants
are collectively referred to as parameter constants. Parameter constants
can be initialized with a literal.
Example17: Declaration of
parameter
localparam
byte colon1 = ":" ;
specparamdelay = 10 ; // specparams are used for specify blocks
parameter
logic flag = 1 ;
System Verilog provides four methods for setting the value of
parameter constants. Each parameter may be assigned a default value when
declared.
The value of a parameter of
an instantiated module, interface or program can be overridden in each instance
using one of the following:
Ø Assignment by ordered list (e.g., m #(value, value) u1 (...); ).
Ø Assignment by name (e.g., m #(.param1(value), .param2(value)) u1
(...); ).
Ø Defparam statements, using
hierarchical path names to redefine each parameter.
The list_of_param_assignments can appear in a module,
interface, program, class, or package or in the parameter_port_list of a
module, interface, program, or class. If the declaration of a design element
uses a parameter_port_list (even an empty one), then in any parameter_declaration
directly contained within the declaration, the parameter keyword
shall be a synonym for the localparam keyword.
All param_assignments appearing
within a class body shall become localparam declarations regardless of
the presence or absence of a parameter_port_list. All param_assignments
appearing within apackage shall become localparam declarations.
The parameter keyword can be omitted in a parameter port
list.
Example18:
Parameterized class, interface and module
classvector #(size = 1); // size is a parameter in a parameter port
list
logic[size-1:0] v;
endclass
interfacesimple_bus #(AWIDTH = 64, type T = word) // parameter port
list
(input
logic clk) ; // port list
...
endinterface
modulemc #(int N = 5, M = N*16, type T = int, T x =
0)
…..
endmodule
In a list of parameter constants, a parameter can depend on
earlier parameters. In the following declaration, the default value of the
second parameter depends on the value of the first parameter. The third
parameter is a type, and the fourth parameter is a value of that type.
In the declaration of a parameter in a parameter port list, the
specification for its default value may be omitted, in which case the parameter
shall have no default value. If no default value is specified for a parameter
of a design element, then an overriding parameter value shall be specified in
every instantiation of that design element. Also, if no default value is
specified for a parameter of a design element, then a tool shall not implicitly
instantiate that design element. If no default value is specified for a
parameter of a class, then an overriding parameter value shall be specified in
every specialization of that class.
A parameter constant can have a type specification and a range
specification. The type and range of parameters shall be in accordance with
the following rules:
Ø A parameter declaration with no type or range specification shall
default to the type and range of the final value assigned to the parameter,
after any value overrides have been applied. If the expression is real, the
parameter is real. If the expression is integral, the parameter is a logic vector
of the same size with range [size-1:0].
Ø A parameter with a range specification, but with no type
specification, shall have the range of the parameter declaration and shall be
unsigned. The sign and range shall not be affected by value overrides.
Ø A parameter with a type specification, but with no range
specification, shall be of the type specified. A signed parameter shall default
to the range of the final value assigned to the parameter, after any value
overrides have been applied.
Ø A parameter with a signed type specification and with a range
specification shall be signed and shall have the range of its declaration. The
sign and range shall not be affected by value overrides.
Ø A parameter with no range specification and with either a signed
type specification or no type specification shall have an implied range with an
lsb equal to 0 and an msb equal to one less than the size of the
final value assigned to the parameter.
In an assignment to, or override of, a parameter with an explicit
type declaration, the type of the right-hand expression shall be assignment
compatible with the declared type. The conversion rules between real and
integer values apply to parameters as well. A specparam can also be set
to an expression containing one or more specparams.
Example19:summary
of declaration of parameter
parametermsb =
7; // defines msb as a constant value 7
parametere =
25, f = 9; // defines two constant numbers
parameterr =
5.7; // declares r as a real parameter
parameter byte_size
= 8,
byte_mask =
byte_size - 1;
parameter average_delay
= (r + f) / 2;
parameter signed
[3:0]
mux_selector = 0;
parameter real r1 =
3.5e17;
parameter p1 =
13'h7e;
parameter [31:0]
dec_const = 1'b1; // value converted to 32 bits
parameter newconst
= 3'h4; // implied range of [2:0]
parameter newconst
= 4; // implied range of at least [31:0]
parameter
logic [31:0]
P1 [3:0] = '{ 1, 2, 3, 4 } ; // unpacked array parameter declaration
A
parameter can also be declared as an aggregate type, such as an unpacked array
or an unpacked structure. An aggregate parameter must be assigned to or
overridden as a whole; individual members of an aggregate parameter may not be
assigned or overridden separately. However, an individual member of an
aggregate parameter may be used in an expression.
Example20: Usage of defparm
moduletop;
logic lk;
logic [0:4]
in1;
logic [0:9]
in2;
wire [0:4]
o1;
wire [0:9]
o2;
vdff m1 (o1,
in1, clk);
vdff m2 (o2,
in2, clk);
endmodule
module vdff
(out, in, clk);
parameter size
= 1, delay = 1;
input [0:size-1]
in;
input clk;
output [0:size-1]
out;
logic[0:size-1]
out;
always@(posedge
clk)
# delay out =
in;
endmodule
module annotate;
defparam
top.m1.size = 5,
top.m1.delay =
10,
top.m2.size =
10,
top.m2.delay =
20;
endmodule
The
module annotate has the defparam statement, which overrides size and
delay parameter values for instances m1 and m2 in the top-level module top. The
modules top and annotate would both be considered top-level modules.
Local parameters are identical to parameters except that they
cannot directly be modified by defparam Statements or instance parameter
value assignments. Local parameters can be assigned constant expressions
containing parameters, which in turn can be modified with defparam statements
or instance parameter value assignments. Local parameters
may be declared in a module’s parameter_port_list.
The keyword specparam declares a special type of parameter
that is intended only for providing timing and delay values, but can appear in
any expression that is not assigned to a parameter and is not part of the range
specification of a declaration. Specify parameters (also called specparams)
are permitted both within the specify block and in the main module body.
A
specify parameter declared outside a specify block shall be declared before it
is referenced. The value assigned to a specify parameter can be any constant
expression. A specify parameter can be used as part of a constant expression
for a subsequent specify parameter declaration. Unlike the parameter constant,
a specify parameter cannot be modified from within the language, but it can be
modified through SDF annotation
moduleRAM16GEN
( output[7:0] DOUT,
input [7:0]
DIN,
input [5:0]
ADR,
input WE,
CE);
specparam dhold
= 1.0;
specparam ddly
= 1.0;
parameter width
= 1;
parameter regsize
= dhold + 1.0; // Illegal - cannot assign specparams to parameters
endmodule
A const
form of constant differs from a localparam constant in that the localparam
shall be set during elaboration, whereas a const can be set during
simulation, such as in an automatic task.
A
static constant declared with the const keyword can be set to an
expression of literals, parameters, local parameters, genvars, enumerated
names, a constant function of these, or other constants. Hierarchical names are
allowed because constants declared with the const keyword are calculated
after elaboration.
const
logic option
= a.b.c ;
An
instance of a class (an object handle) can also be declared with the const keyword.
Const
class_name
object = new(5,3);
Variables declared outside a module, program, interface, checker,
task or functions are local to the compilation unit and have a static lifetime
(exist for the whole simulation). This is roughly equivalent to C static
variables declared outside a function, which are local to a file.
Variables declared inside a module, interface, program, or
checker, but outside a task, process, or function, are local in scope and have
a static lifetime.
Variables declared inside a static task, function, or block are
local in scope and default to a static life time. Specific variables within a
static task, function, or block can be explicitly declared as automatic. Such
variables have the lifetime of the call or block and are initialized on each
entry to the call or block. This is roughly equivalent to a C automatic
variable.
Tasks and functions may be declared as automatic. Variables
declared in an automatic task, function, or block are local in scope, default
to the lifetime of the call or block and are initialized on each entry to the
call or block.
An automatic block is one
in which declarations are automatic by default. Specific variables within an
automatic task, function, or block can be explicitly declared as static. Such
variables have a static lifetime. This is roughly equivalent to C static
variables declared within a function.
The lifetime of a fork-join block shall encompass the execution of
all processes spawned by the block. The lifetime of a scope enclosing any
fork-join block includes the lifetime of the fork-join block.
Example21: lifetime of
variables in module
Module
msl;
intst0; // static
initial
begin
intst1; // static
static
int st2; // static
automatic
int auto1; // automatic
end
task
automatic t1();
intauto2; // automatic
static
int st3; // static
automatic int auto3;
// automatic
endtask
endmodule
Variables
declared in a static task, function, or procedural block default to a static
lifetime and a local scope. However, an explicit static keyword shall be
required when an initialization value is specified as part of a static
variable’s declaration to indicate the user’s intent of executing that
initialization only once at the beginning of simulation. The static keyword
shall be optional where it would not be legal to declare the variables as
automatic.
Example22:
lifetime of variables in for loops
module
top_legal;
int svar1
= 1; // static keyword optional
initial begin
for(int
i=0; i<3; i++) begin
automatic int loop3
= 0; // executes every loop
for(int
j=0; j<3; j++) begin
loop3++;
$display(loop3);
end
end//
prints 1 2 3 1 2 3 1 2 3
for(int
i=0; i<3; i++) begin
static int loop2
= 0; // executes once before time 0
for(int
j=0; j<3; j++) begin
loop2++;
$display(loop2);
end
end//
prints 1 2 3 4 5 6 7 8 9
end
endmodule :
top_legal
module top_illegal;
// should not compile
initial begin
int svar2
= 2; // static/automatic needed to show intent
for(int
i=0; i<3; i++) begin
intloop3
= 0; // illegal statement
for(int
i=0; i<3; i++) begin
loop3++;
$display(loop3);
end
end
end
endmodule :
top_illegal
An
optional qualifier can be used to specify the default lifetime of all variables
declared in a task, function, or block defined within a module, interface,
package, or program. The lifetime qualifier is automatic or static.
The default lifetime is static.
Example23:lifetime
of variables in program block
program
automatic test
;
inti; //
not within a procedural block - static
taskt ( int
a ); // arguments and variables in t are automatic
... // unless
explicitly declared static
endtask
endprogram
Class methods and declared for loop variables are by
default automatic, regardless of the lifetime attribute of the scope in which
they are declared.
Automatic variables and members or elements of dynamic
variables—class properties and dynamically sized variables—shall not be written
with nonblocking, continuous, or procedural continuous assignments. References
to automatic variables and elements or members of dynamic variables shall be
limited to procedural blocks.
A
data type can be changed by using a cast ( ’) operation. In a static cast, the expression to be cast shall be
enclosed in parentheses that are prefixed with the casting type and an
apostrophe. If the expression is assignment compatible with the casting type,
then the cast shall return the value that a variable of the casting type would
hold after being assigned the expression.
int'(2.0
* 3.0)
shortint'({8'hFA,8'hCE})
Thus,
in the following example, if expressions expr_1 and expr_2 are assignment compatible
with data types cast_t1 and cast_t2, respectively, then
A
= cast_t1'(expr_1) + cast_t2'(expr_2);
If
the casting type is a constant expression with a positive integral value, the
expression in parentheses shall be padded or truncated to the size specified.
It shall be an error if the size specified is zero or negative.
Examples24:casting
for parameters
17’(x - 2)
parameterP =
16;
(P+1)’(x – 2)
The
signedness can also be changed.
signed’(x)
A structure
represents a collection of data types that can be referenced as a whole, or
the individual data types that make up the structure can be referenced by name.
By default, structures are unpacked, meaning that there is an
implementation-dependent packing of the data types. Unpacked structures can
contain any data type.
Structure
declarations follow the C syntax, but without the optional structure tags
before the ‘{’.
Example25: Declaring of unpacked structures
struct {bit
[7:0] opcode; bit [23:0] addr; }IR; // anonymous structure defines
variable IR
IR.opcode = 1;
// set field in IR.
typedef struct {
bit[7:0]
opcode;
bit[23:0]
addr;
} instruction;
// named structure type
instruction IR;
// define variable
A
packed structure is a mechanism for subdividing a vector into subfields, which
can be conveniently accessed as members. Consequently, a packed structure
consists of bit fields, which are packed together in memory without gaps. An
unpacked structure has an implementation-dependent packing. A packed structure
differs from an unpacked structure in that, when a packed structure appears as
a primary, it shall be treated as a single vector.
A
packed structure can be used as a whole with arithmetic and logical operators.
The first member specified is the most significant and subsequent members
follow in decreasing significance. The structures are declared using the packed
keyword, which can be followed by the signed or unsigned keyword,
according to the desired arithmetic behavior. The default is unsigned.
Example26: Declaring of packed structures
struct packed
signed {
inta;
shortintb;
bytec;
bit[7:0]
d;
} pack1; //
signed, 2-state
struct packed
unsigned {
timea;
integerb;
logic[31:0]
c;
} pack2; //
unsigned, 4-state
The
signing of unpacked structures is not allowed. The following declaration would
be considered illegal:
Example27: Declaring of unpacked structures
(illegal)
typedef
struct signed {
int f1 ;
logic
f2 ;
}
sIllegalSignedUnpackedStructType; // illegal declaration
If all data types within a packed structure are 2-state, the
structure as a whole is treated as a 2-state vector. If any data type within a
packed structure is 4-state, the structure as a whole is treated as a 4-state
vector. If there are also 2-state members in the structure, there is an
implicit conversion from 4-state to 2-state when reading those members and from
2-state to 4-state when writing them. Only packed data types and the integer
data types shall be legal in packed structures. A packed structure can be used
with a typedef.
typedef
struct packed { // default unsigned
bit [3:0] GFC;
bit [7:0] VPI;
bit [11:0] VCI;
bit CLP;
bit [3:0] PT ;
bit [7:0] HEC;
bit[47:0] [7:0] Payload;
bit[2:0] filler;
}
s_atmcell;
A structure can be assigned as a whole and passed to or from a
subroutine as a whole. Members of a structure data type can be assigned
individual default member values by using an initial assignment with the
declaration of each member. The assigned expression shall be a constant
expression. An example of initializing members of a structure type is as
follows:
typedef
struct {
intaddr = 1 + constant;
intcrc;
bytedata [4] = '{4{1}};
}
packet1;
The
structure can then be instantiated.
packet1
p1; // initialization defined by the typedef. p1.crc will use the default value
for an int
If an explicit initial value expression is used with the
declaration of a variable, the initial assignment expression within the
structure data type shall be ignored.
packet1 pi = '{1,2,'{2,3,4,5}}; //suppresses the typedef
initialization
A union
is a data type that represents a single piece of storage which can be
accessed using one of the named member data types. Only one of the data types
in the union can be used at a time.
Example28:
Declaration of union
Typedef union
{ int i; shortreal f; } num; // named union type
num n;
n.f = 0.0; //
set n in floating point format
typedef struct {
bitisfloat;
union{int
i; shortreal f; } n; // anonymous union type
} tagged_st; //
named structure
If no
initial value is specified in the declaration of a variable of an unpacked
union type, then the variable shall be initialized to the default initial value
for variables of the type of the first member in declaration order of the union
type.
Packed
unions shall only contain members that are of integral data types. A packed union can also be used as a whole with arithmetic
and logical operators, and its behavior is determined by the signed or unsigned
keyword, the latter being the default. One or more bits of a packed union
can be selected as if it were a packed array with the range [n-1:0].Only packed
data types and the integer data types shall be legal in packed unions.
If a packed union contains a 2-state member and a 4-state member,
the entire union is 4-state. There is an implicit conversion from 4-state to
2-state when reading and from 2-state to 4-state when writing the 2-state
member. For example, a union can be accessible with different access widths:
Example28: Declaring of packed unions
typedef
union packed { // default unsigned
s_atmcell
acell;
bit[423:0] bit_slice;
bit[52:0][7:0] byte_slice;
}
u_atmcell;
u_atmcell
u1;
byteb; bit [3:0] nib;
b =
u1.bit_slice[415:408]; // same as b = u1.byte_slice[51];
nib
= u1.bit_slice [423:420]; // same as nib = u1.acell.GFC;
With packed unions, writing one member and reading another is
independent of the byte ordering of the machine, unlike an unpacked union of
unpacked structures, which are C-compatible and have members in ascending
address order.
The qualifier tagged in a union declares it as a tagged
union, which is a type-checked union. An ordinary (untagged) union can be
updated using a value of one member type and read as a value of another member
type, which is a potential type loophole. A tagged union stores both the member
value and a tag, i.e., additional bits representing the current member
name. The tag and value can only be updated together consistently using a
statically type-checked tagged union expression. The member value can only be
read with a type that is consistent with the current tag value (i.e., member
name). Thus, it is impossible to store a value of one type and (mis)interpret
the bits as another type.
Example29: Creation of tagged unions
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;
A
value of Instr type is either an Add instruction, in which case it contains
three 5-bit register fields, or it is a Jmp instruction. In the latter case, it
is either an unconditional jump, in which case it contains a 10-bit destination
address, or it is a conditional jump, in which case it contains a 2-bit
condition-code register field and a 10-bit destination address.
Ø The
size is always equal to the number of bits needed to represent the tag plus the
maximum of thesizes of the members.
Ø The
size of the tag is the minimum number of bits needed to code for all the member
names (e.g.,five to eight members would need 3 tag bits).
Ø The
tag bits are always left-justified (i.e., towards the MSBs).
Ø For
each member, the member bits are always right-justified [i.e., towards the
least significant bits(LSBs)].
Ø The
bits between the tag bits and the member bits are undefined. In the extreme
case of a void member,only the tag is significant and all the remaining bits
are undefined.
System
Verilog supports both packed arrays and unpacked arrays of data. The term packed
array is used to refer to the dimensions declared before the data
identifier name. The term unpacked array is used to refer to the
dimensions declared after the data identifier name.
bit[7:0]
c1; // packed array of scalar bit types
realu
[7:0]; // unpacked array of real types
A
packed array is a mechanism for subdividing a vector into subfields, which can
be conveniently accessed as array elements. Consequently, a packed array is
guaranteed to be represented as a contiguous set of bits. An unpacked array may
or may not be so represented. A packed array differs from an unpacked array in
that, when a packed array appears as a primary, it is treated as a single
vector.
Packed arrays can be made of only the single bit data types (bit,
logic, reg), enumerated types, and recursively other packed
arrays and packed structures. Integer types with predefined widths shall not
have packed array dimensions declared. These types are byte, shortint,
int, longint, integer, and time. Although an
integer type with a predefined width n is not a packed array, it matches, and
can be selected from as if it were, a packed array type with a single [n-1:0]
dimension.
bytec2; // same as bit signed [7:0] c2;
integeri1; // same as logic signed [31:0] i1;
Unpacked arrays can be made of any data type. Arrays whose
elements are themselves arrays are declared as multidimensional arrays.
Unpacked arrays shall be declared by specifying the element address range(s)
after the declared identifier.
Elements of net arrays can be used in the same fashion as a scalar
or vector net. Net arrays are useful for connecting to ports of module
instances inside loop generate constructs.
Each fixed-size dimension shall be represented by an address
range, such as [1:1024], or a single positive number to specify the size of a
fixed-size unpacked array, as in C. In other words, [size] becomes the same as
[0:size-1]. The following examples declare equivalent size two-dimensional
fixed-size arrays of int variables:
Int Array[0:7][0:31]; // array declaration using ranges
Int Array[8][32]; // array declaration using sizes
The
expressions that specify an address range shall be constant integer
expressions. The value of the constant expression can be a positive integer, a
negative integer, or zero. It shall be illegal for them to contain any unknown
(x) or high-impedance bits.
The
following operations can be performed on all arrays, packed or unpacked. The
examples provided with these rules assume that A and B are arrays of the same
shape and type.
Ø Reading
and writing the array, e.g., A = B
Ø Reading
and writing a slice of the array, e.g., A[i:j] = B[i:j]
Ø Reading
and writing a variable slice of the array, e.g., A[x+:c] = B[y+:c]
Ø Reading
and writing an element of the array, e.g., A[i] = B[i]
Ø Equality
operations on the array or slice of the array, e.g., A==B, A[i:j] != B[i:j]
The
following operations can be performed on packed arrays, but not on unpacked
arrays. The examples provided with these rules assume that A is an array.
Ø Assignment
from an integer, e.g., A = 8’b11111111;
Ø Treatment
as an integer in an expression, e.g., (A + 3)
If an
unpacked array is declared as signed, then this applies to the individual
elements of the array because the whole array cannot be viewed as a single
vector.
A
one-dimensional array with elements of typesreg, logic or bit is
also called a memory. Memory arrays can be used to model read-only
memories (ROMs), random access memories (RAMs), and register files. An element
of the packed dimension in the array is known as a memory element or word.
logic[7:0]
mema [0:255]; // declares a memory array of 256 8-bit elements. The array
indices are 0 to 255
mema[5] = 0; //
Write to word at address 5
data
= mema[addr]; // Read word at address indexed by addr
A multidimensional
array is an array of arrays. Multidimensional arrays can be declared by
including multiple dimensions in a single declaration. The dimensions preceding
the identifier set the packed dimensions. The dimensions following the
identifier set the unpacked dimensions.
bit[3:0]
[7:0] joe [1:10]; // 10 elements of 4 8-bit bytes (each element packed into 32
bits) can be used as follows:
joe[9] = joe[8]
+ 1; // 4 byte add
joe[7][3:2] =
joe[6][1:0]; // 2 byte copy
Multiple
unpacked dimensions can also be defined in stages with typedef.
typedefbsix
mem_type [0:3]; // array of four ’bsix’ elements
mem_type ba
[0:7]; // array of eight ’mem_type’ elements
A subarray
is an array that is an element of another array. Omitting indices for all
the dimensions references the entire array.
intA[2][3][4],
B[2][3][4], C[5][4];
...
A[0][2]
= B[1][1]; // assign a subarray composed of four ints
A[1] = B[0]; //
assign a subarray composed of three arrays of
four ints each
A =
B; // assign an entire array
A[0][1] = C[4];
// assign compatible subarray of four ints
A
comma-separated list of array declarations can be specified. All arrays in the
list shall have the same data type and the same packed array dimensions.
bit[7:0]
[31:0] v7 [1:5] [1:10], v8 [0:255]; // two arrays declared
An
expression can select part of a packed array, or any integer type, which is
assumed to be numbered down to 0.The term part-selectrefers to a
selection of one or more contiguous bits of a single-dimension packed array.
logic[63:0]
data;
logic[7:0]
byte2;
byte2 =
data[23:16]; // an 8-bit part-select from data
A
single element of a packed or unpacked array can be selected using an indexed
name.
bit[3:0]
[7:0] j; // j is a packed array
bytek;
k = j[2]; //
select a single 8-bit element from j
One
or more contiguous elements can be selected using a slice name. A slice name of
a packed array is a packed array. A slice name of an unpacked array is an unpacked
array.
bit signed [31:0]
busA [7:0] ; // unpacked array of 8 32-bit vectors
intbusB
[1:0]; // unpacked array of 2 integers
busB =
busA[7:6]; // select a 2-vector slice from busA
The size of the
part-select or slice shall be constant, but the position can be variable.
inti =
bitvec[j +: k]; // k must be constant.
inta[x:y],
b[y:z], e;
a = {b[c -: d],
e}; // d must be constant
Slices
of an array can only apply to one dimension, but other dimensions can have
single index values in anexpression.
A dynamic array is an unpacked array whose size can be set or
changed at run time. The default size of an uninitialized dynamic array is
zero. The size of a dynamic array is set by the new constructor or array
assignmentrespectively. Dynamic arrays support all variable data types as
element types, including arrays.
Dynamic array dimensions are denoted in the array declaration by [
]. Any unpacked dimension in an array declaration may be a dynamic array
dimension.
For
example:
bit[3:0] nibble[]; // Dynamic array of 4-bit vectors
integer
mem[2][]; // Fixed-size unpacked array composed of 2 dynamic
subarrays of integers
Note that in order for an identifier to represent a dynamic array,
it must be declared with a dynamic array dimension as the leftmost unpacked
dimension.
The new
constructor sets the size of a dynamic array and initializes its elements.
It may appear in place of the right-hand side expression of variable
declaration assignments and blocking procedural assignments when the left-hand
side indicates a dynamic array.
The new
constructor follows the System Verilog precedence rules. Because both the
square brackets [] and the parenthesis () have the same precedence, the
arguments to the new constructor are evaluated left to right: [ expression]
first, and ( expression ) second.
Dynamic
array declarations may include a declaration assignment with the new constructor
as the right-hand side:
intarr1
[][2][3] = new [4]; // arr1 sized to length 4; elements are fixed-size arrays and so do not require
initializing
intarr2
[][] = new [4]; // arr2 sized to length 4; dynamic subarrays remain
unsized and uninitialized
intarr3
[1][2][] = new [4]; // Error – arr3 is not a dynamic array, though it
contains dynamic subarrays.
Dynamic
arrays may be initialized in procedural contexts using the new constructor
in blocking
assignments:
intarr[2][][];
arr[0] = new [4];
// dynamic subarray arr[0] sized to length 4
arr[0][0] = new
[2]; // legal, arr[0][n] created above for n = 0..3
arr[1][0] = new
[2]; // illegal, arr[1] not initialized so arr[1][0] does not exist
arr[0][] = new
[2]; // illegal, syntax error - dimension without subscript on left hand
side
arr[0][1][1] = new[2];
// illegal, arr[0][1][1] is an int, not a dynamic array
The
optional initialization expression is used to initialize the dynamic array.
When present, it shall be an array that is assignment-compatible with the
left-hand-side dynamic array.
Int idest[], isrc[3] = '{5, 6, 7};
idest = new [3] (isrc); // set
size and array element data values (5, 6, 7)
The
size argument need not match the size of the initialization array. When the
initialization array’s size is greater, it is truncated to match the size
argument; when it is smaller, the initialized array is padded with default
values to attain the specified size.
Int src[], dest1[], dest2[];
src = new [3] ('{2, 3, 4});
dest1 = new[2] (src); // dest1’s
elements are {2, 3}.
dest2 = new[4] (src); // dest1’s
elements are {2, 3, 4, 0}.
This
behavior provides a mechanism for resizing a dynamic array while preserving its
contents. An existing dynamic array can be resized by using it both as the
left-hand side term and the initialization expression.
integeraddr[]; // Declare the dynamic array.
addr = new[100]; // Create a
100-element array.
...
// Double the array size, preserving
previous values.
// Preexisting references to elements of
addr are outdated.
addr = new[200](addr);
Resizing
or reinitializing a previously-initialized dynamic array using new is
destructive; no preexistingarray data is preserved, and all preexisting
referencesto array elements become outdated.
a)
Size()
The
prototype for the size() method is as follows:
function int size();
The size() method returns the current
size of a dynamic array or returns zero if the array has not beencreated.
intj = addr.size;
addr = new[ addr.size() * 4 ]
(addr); // quadruple addr array
b) Delete()
The prototype for the delete() method is
as follows:
function void delete();
The
delete() method empties the array, resulting in a zero-sized array.
intab [] = new[ N ]; // create a temporary array of size N
// use ab
ab.delete; // delete the array contents
$display(
"%d", ab.size ); // prints 0
Dynamic
arrays are useful for dealing with contiguous collections of variables whose
number changes dynamically. When the size of the collection is unknown or the
data space is sparse, an associative array is a better option. Associative
arrays do not have any storage allocated until it is used, and the index
expression is not restricted to integral expressions, but can be of any type.
An
associative array implements a lookup table of the elements of its declared
type. The data type to be used as an index serves as the lookup key and imposes
an ordering.
The
syntax to declare an associative array is as follows:
data_type
array_id [ index_type ];
Where
data_type is the data type of the array elements. Can be any type allowed for
fixed-size arrays.array_id is the name of the array
being declared. index_type is the data-type to be used as an index or is *.
If * is specified, then the array is indexed by any integral expression
of arbitrary size. An index type restricts the indexing expressions to a particular
type. It shall be illegal for index_type to declare a type.
Examples
of associative array declarations are as follows:
integeri_array[*]; // associative array of integer (unspecified index)
bit[20:0] array_b[string]; // associative array of 21-bit
vector, indexed by string
Array elements in associative arrays are allocated dynamically; an
entry is created the first time it is written. The associative array maintains
the entries that have been assigned values and their relative order according to
the index data type. Associative array elements are unpacked. In other words,
other than for copying or comparing arrays, an individual element must be
selected out of the array before it can be used in most expressions.
For example:
intarray_name [*];
Associative arrays that specify a
wildcard index type have the following properties:
Ø The array may be indexed by any integral expression. Because the
index expressions may be of different sizes, the same numerical value can have multiple
representations, each of a different size. SystemVerilog resolves this
ambiguity by removing the leading zeros and computing the minimal length and
using that representation for the value.
Ø Non integral index values are illegal and result in an error.
A 4-state index value containing X or Z is invalid. Indexing
expressions are self-determined and treated as unsigned. A string literal index
is automatically cast to a bit vector of equivalent size. The ordering is
numerical (smallest to largest).
Associative arrays that specify a wildcard index type shall not be
used in a foreach loop or with an array manipulation method that returns
an index value or array of values.
For
example:
intarray_name [ string];
Associative arrays that specify a string index have the following
properties:
Ø Indices can be strings or string literals of any length. Other
types are illegal and shall result in a typecheck error.
Ø An empty string “” index is valid.
Ø The ordering is lexicographical (lesser to greater).
For example:
intarray_name1
[ integer];
typedef bit
signed [4:1]
SNibble;
intarray_name2
[ SNibble ];
typedef bit [4:1]
UNibble;
intarray_name3
[ UNibble ];
Associative
arrays that specify an index of integral data type shall have the following
properties:
Ø The
index expression shall be evaluated in terms of a cast to the index type,
except that an implicit cast from a real or shortreal data type shall be
illegal.
Ø A
4-state index expression containing X or Z is invalid.
Ø The
ordering is signed or unsigned numerical, depending on the signedness of the
index type.
In
addition to the indexing operators, several built-in methods are provided,
which allow users to analyze and manipulate associative arrays, as well as iterate
over its indices or keys.
a) Num()
and size()
The syntax for
the num() and size() methods is as follows:
function int num();
function int size();
The num() and
size() methods return the number of entries in the associative array. If the
array is empty, they return 0.
Int imem[int];
imem[ 2’b3 ] =
1;
imem[ 16’hffff ]
= 2;
imem[ 4b’1000 ]
= 3;
$display(
"%0d entries\n", imem.num ); // prints "3 entries"
b) Delete()
The syntax for
the delete() method is as follows:
function void delete(
[input index] );
where index is
an optional index of the appropriate type for the array in question.
If the index is
specified, then the delete() method removes the entry at the specified index.
If the entry to be deleted does not exist, the method issues no warning.
If the index is
not specified, then the delete() method removes all the elements in the array.
intmap[string
];
map[
"hello" ] = 1;
map[
"sad" ] = 2;
map[
"world" ] = 3;
map.delete(
"sad" ); // remove entry whose index is "sad" from
"map"
map.delete; //
remove all entries from the associative array "map"
c) Exists()
The syntax for
the exists() method is as follows:
function int exists(
input index );
where index is
an index of the appropriate type for the array in question.
The exists()
function checks whether an element exists at the specified index within the
given array. It returns 1 if the element exists; otherwise, it returns 0.
if(
map.exists( "hello" ))
map[
"hello" ] += 1;
else
map[
"hello" ] = 0;
d) First()
The syntax for
the first() method is as follows:
function int first(
ref index );
where index is
an index of the appropriate type for the array in question. Associative arrays
that specify awildcard index type shall not be allowed. The first() method
assigns to the given index variable the value of the first (smallest) index in
the associative array. It returns 0 if the array is empty; otherwise, it
returns 1.
strings;
if(
map.first( s ) )
$display(
"First entry is : map[ %s ] = %0d\n", s, map[s] );
e) Last()
The syntax for
the last() method is as follows:
function int last(
ref index );
where index is
an index of the appropriate type for the array in question. Associative arrays
that specify a wildcard index type shall not be allowed. The last() method
assigns to the given index variable the value of the last (largest) index in
the associative array. It returns 0 if the array is empty; otherwise, it
returns 1.
strings;
if(
map.last( s ) )
$display(
"Last entry is : map[ %s ] = %0d\n", s, map[s] );
f) Next()
The syntax for
the next() method is as follows:
function int next(
ref index );
where index is
an index of the appropriate type for the array in question. Associative arrays
that specify a wildcard index type shall not be allowed. The next() method
finds the smallest index whose value is greater than the given index argument.
If there is a next entry, the index variable is assigned the index of the next
entry, and the function returns 1. Otherwise, the index is unchanged, and the
function returns 0.
strings;
if(
map.first( s ) )
do
$display(
"%s : %d\n", s, map[ s ] );
while(
map.next( s ) );
g) Prev()
The syntax for
the prev() method is as follows:
function int prev(
ref index );
where index is
an index of the appropriate type for the array in question. Associative arrays
that specify a
wildcard index
type shall not be allowed. The prev() function finds the largest index whose
value is smaller than the given index argument. If there is a previous entry,
the index variable is assigned the index of the previous entry, and the
function returns 1. Otherwise, the index is unchanged, and the function returns
0.
strings;
if(
map.last( s ) )
do
$display(
"%s : %d\n", s, map[ s ] );
while(
map.prev( s ) );
A
queue is a variable-size, ordered collection of homogeneous elements. A queue
supports constant-time access to all its elements as well as constant-time
insertion and removal at the beginning or the end of the queue. Each element in
a queue is identified by an ordinal number that represents its position within
the queue, with 0 representing the first, and $ representing the last. A
queue is analogous to a one-dimensional unpacked array that grows and shrinks
automatically. Thus, like arrays, queues can be manipulated using the indexing,
concatenation, slicing operator syntax, and equality operators.
Queues are
declared using the same syntax as unpacked arrays, but specifying $ as
the array size.
The
maximum size of a queue can be limited by specifying its optional right bound
(last index).Queue values may be written using assignment patterns or unpacked
array concatenations.
For example:
Byte q1[$];
// A queue of bytes
string names[$]
= { "Bob" }; // A queue of strings with one element
integer Q[$]
= { 3, 2, 7 }; // An initialized queue of integers
bitq2[$:255];
// A queue whose maximum size is 256 bits
The
empty array literal {} is used to denote an empty queue. If an initial value is
not provided in the declaration, the queue variable is initialized to the empty
queue.
Queues shall support the same operations that can be performed on
unpacked arrays. In addition, queues shall support the following operations:
Ø A queue shall resize itself to accommodate any queue value that is
written to it, except that its maximum size may be bounded.
Ø In a queue slice expression such as Q[a:b], the slice bounds may
be arbitrary integral expressions and, in particular, are not required to be
constant expressions.
Ø Unlike arrays, the empty queue, {}, is a valid queue and the
result of some queue operations. The following rules govern queue operators:
v Q[ a : b ] yields a queue with b - a + 1 elements.
v If a > b, then Q[a:b] yields the empty queue {}.
v Q[ n : n ] yields a queue with one item, the one at position n.
Thus, Q[ n : n ] === {Q[n] }.
v If n lies outside Q’s range (n < 0 or n > $), then Q[n:n]
yields the empty queue {}.
v If either a or b are 4-state expressions containing X or Z values,
it yields the empty queue {}.
v Q[ a : b ] where a < 0 is the same as Q[ 0 : b ].
v Q[ a : b ] where b > $ is the same as Q[ a : $ ].
v An invalid index value (i.e., a 4-state expression with X’s or
Z’s, or a value that lies outside 0...$)shall cause a read operation (e = Q[n])
to return the default initial value for the type of queue item
v An invalid index (i.e., a 4-state expression with X’s or Z’s, or a
value that lies outside 0...$+1) shallcause a write operation to be ignored and
a run-time warning to be issued; however, writing toQ[$+1] is legal.
In
addition to the array operators, queues provide several built-in methods.
Assume these declarations for the examples that follow:
Typedef mytype
element_t; // mytype is any legal type for a queue
Typedef element_t
queue_t[$];
element_t e;
queue_t Q;
inti;
a) Size()
The prototype
for the size() method is as follows:
function int size();
The size() method
returns the number of items in the queue. If the queue is empty, it returns 0.
for(int
j = 0; j < Q.size; j++ ) $display( Q[j] );
b) Insert()
The prototype of
the insert() method is as follows:
function void insert(input
int index, input element_t item);
The
insert() method inserts the given item at the specified index position.
c) Delete()
The syntax for
the delete() method is as follows:
function void delete(
[int index] );
where index is
an optional index.
If the index is
specified, then the delete() method deletes the item at the specified index
position.
If the index is
not specified, then the delete() method deletes all the elements in the queue,
leaving the queue empty.
d) Pop_front()
The prototype of
the pop_front() method is as follows:
Function element_t
pop_front();
The pop_front()
method removes and returns the first element of the queue.
e) Pop_back()
The prototype of
the pop_back() method is as follows:
Function element_t
pop_back();
The pop_back()
method removes and returns the last element of the queue.
f) Push_front()
The prototype of
the push_front() method is as follows:
function void push_front(input
element_t item);
The push_front()
method inserts the given element at the front of the queue.
g) Push_back()
The prototype of
the push_back() method is as follows:
function void push_back(input
element_t item);
The push_back()
method inserts the given element at the end of the queue.
Example
illustrating the queue functions:
intq[$]
= { 2, 4, 8 };
inte,
pos, junk;
// assignment //
method call yielding the
// // same value
in variable q
//
----------------------------- // -------------------------
q = { q, 6 }; //
q.push_back(6)
q = { e, q }; //
q.push_front(e)
q = q[1:$]; //
q.pop_front(junk) or q.delete(0)
q = q[0:$-1]; //
q.pop_back(junk) or q.delete(q.size-1)
q = {
q[0:pos-1], e, q[pos:$] }; // q.insert(pos, e)
q = { q[0:pos],
e, q[pos+1:$] }; // q.insert(pos+1, e)
q = {}; //
q.delete()
Some
useful operations that cannot be implemented as a single queue method call are illustrated
in the following examples. As in the examples above, assignment to the queue
variable outdates any reference to its elements.
q = q[2:$]; // a
new queue lacking the first two items
q = q[1:$-1]; //
a new queue lacking the first and last items
Operators are single-, double-, or triple-character sequences and
are used in expressions. Unary operators shall appear to the left of
their operand. Binary operators shall appear between their operands.
A conditional operator shall have two operator characters
that separate three operands.
List of all the operators supported in System Verilog has been
given in the table
Table 2: Overall operators
In addition to the simple assignment operator, =, SystemVerilog
includes the C assignment operators and special bitwise assignment operators:
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, <<<=,>>>=.
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;
Following are the new SystemVerilog assignment operators
and its equivalent in Verilog
Assignments
in expressions:
In SystemVerilog, an expression can include a
blocking assignment. 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.
if(a=b) // error in systemverilog
(a=b) statement assigns ‘b’ value to ‘a’ and then
returns ‘a’ value.
if((a=b)) // isequivalentto
a=b;
if(a)
{} concatenation right
of assignment.
´{} concatenation left of assignment.
A
concatenation is the result of the joining together of bits resulting from one
or more expressions. The concatenation shall be expressed using the brace
characters ‘{’ and ‘}’, with commas separating the expressions within.
Unsized
constant numbers shall not be allowed in concatenations. This is because the
size of each operand inthe concatenation is needed to calculate the complete
size of the concatenation.
The
concatenation is treated as a packed vector of bits. It can be used on the
left-hand side of an assignmentor in an expression.
Example:
logiclog1, log2,
log3;
{log1, log2, log3} = 3’b111;
{log1, log2, log3} = {1’b1, 1’b1, 1’b1}; // same
effect as 3’b111
# >>= <<=
relational
The different types of equality (and inequality)
operators in SystemVerilog behave differently when their operands contain
unknown values (X or Z). The == and != operators may 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 may result in X if the left operand
contains an X or Z that is not being comparedwith a wildcardin the right
operand.
SystemVerilog added two new logical operators
logical implication (->), and logical equivalence (<->). The logical
implication expression1 -> expression2 is logically equivalent to
(!expression1 || expression2), and the logical equivalence expression1
<-> expression2 is logically equivalent to ((expression1 ->
expression2) && (expression2 -> expression1)).
In System Verilog, bitwise exclusive nor has two
notations (~^ and ^~).
The bitwise
operators shall perform bitwise manipulations on the operands; that is, the
operator shall combinea bit in one operand with its corresponding bit in the
other operand to calculate 1 bit for the result.
The
unary reduction operators shall perform a bitwise operation on a single
operand to produce a single-bit result.
The left shift operators, << and <<<, shall
shift their left operand to the left by the number by the number of bit
positions given by the right operand. In both cases, the vacated bit positions
shall be filled with zeros. The right shift operators, >> and
>>>, shall shift their left operand to the right by the number of bit
positions given by the right operand. The logical right shift shall fill the
vacated bit positions with zeros. The arithmetic right shift shall fill the
vacated bit positions with zeros if the result type is unsigned. It shall fill
the vacated bit positions with the value of the most significant (i.e., sign)
bit of the left operand if the result type is signed. If the right operand has
an x or z value, then the result shall be unknown. The right operand is always
treated.
++
increment
--
decrement
SystemVerilog includes the C 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.
2.16.11.
Set
# inside !inside dist
SystemVerilog supports singular value sets and
set membership operators.
The syntax for the set membership operator is:
inside_expression ::= expression inside {
open_range_list }
The expression on the left-hand side of the
inside operator is any singular expression. The set-membership open_range_list
on the right-hand side of the inside operator is a comma-separated list of
expressions or ranges. Values can be repeated, so values and value ranges can
overlap. The order of evaluation of the expressions and ranges is
non-deterministic.
o () Highest precedence
o ++ --
o & ~& | ~| ^ ~^ ~ >< -
o (unary)
o * / %
o + -
o <<>>
o <<= >>= in !in dist
o =?= !?= == != === !==
o &&~
o ^ ^~
o | |~
o &&
o ||
o ?:
o = += -= *= /= %=
o <<= >>= &= |= ^= ~&= ~|= ~^=
Lowest precedence