SystemVerilog for Design Edition 2 Chapter 3 SystemVerilog Literal Values and Built-in Data Types
SystemVerilog for Design Edition 2 Chapter 3
SystemVerilog extends Verilog’s built-in variable types, and enhances how literal values can be specified. This chapter
explains these enhancements and offers recommendations on proper usage. A number of small examples illustrate these enhancements in context. Subsequent chapters contain other examples that utilize SystemVerilog’s enhanced variable types and literal values. The next chapter covers another important enhancement to variable types, user-defined types.
The enhancements presented in this chapter include:
• Enhanced literal values
• ‘define text substitution enhancements
• Time values
• New variable types
• Signed and unsigned types
• Variable initialization
• Static and automatic variables
• Casting
• Constants
3.1 Enhanced literal value assignments
filling a vector with a literal value
In the Verilog language, a vector can be easily filled with all zeros, all Xs (unknown), or all Zs (high-impedance).
parameter SIZE = 64;
reg [SIZE-1:0] data;
data = 0; // fills all bits of data with zero
data = 'bz; // fills all bits of data with Z
data = 'bx; // fills all bits of data with X
Each of the assignments in the example above is scalable. If the SIZE parameter is redefined, perhaps to 128, the assignments will automatically expand to fill the new size of data. However, Verilog does not provide a convenient mechanism to fill a vector with all ones. To specify a literal value with all bits set to one, a fixed size must be specified. For example:
data=64'hFFFFFFFFFFFFFFFF;
This last example is not scalable. If the SIZE constant is redefined to a larger size, such as 128, the literal value must be manually changed to reflect the new bit size of data. In order to make an assignment of all ones scalable, Verilog designers have had to learn coding tricks, such as using some type of operation to fill a vector with all ones, instead of specifying a literal value. The next two examples illustrate using a ones complement operator and a two’s complement operator to fill a vector with all ones:
data = ~0; // one's complement operation
data = -1; // two's complement operation
special literal value for filling a vector
SystemVerilog enhances assignments of a literal value in two ways. First, a simpler syntax is added, that allows specifying the fill value without having to specify a radix of binary, octal or hexadecimal. Secondly, the fill value can also be a logic 1. The syntax is to specify the value with which to fill each bit, preceded by an apostrophe ( ' ), which is sometimes referred to as a “tick”. Thus:
• '0 fills all bits on the left-hand side with 0
• '1 fills all bits on the left-hand side with 1
• 'z or 'Z fills all bits on the left-hand side with z
• 'x or 'X fills all bits on the left-hand side with x
Note that the apostrophe character ( ' ) is not the same as the grave accent ( ` ), which is sometimes referred to as a “back tick”. Using SystemVerilog, a vector of any width can be filled with all ones without hard coding the width of the value to be assigned, or
using operations.
data = '1; // fills all bits of data with 1
literal values scale with the size of the lefthand side vector
This enhancement to the Verilog language simplifies writing models that work with very large vector sizes. The enhancement also makes it possible to code models that automatically scale to new vector sizes without having to modify the logic of the model. This automatic scaling is especially useful when using initializing variables that have parameterized vector widths.
SystemVerilog extends the ability of Verilog’s ‘define text substitution macro by allowing the macro text to include certain special characters.
3.2 ‘define enhancements
3.2.1 Macro argument substitution within strings
Verilog allows the quotation mark ( " ) to be used in a ‘define macro, but the text within the quotation marks became a literal string. This means that in Verilog, it is not possible to create a string using text substitution macros where the string contains embedded macro arguments.
In Verilog, the following example will not work as intended:
`define print(v) \
$display("variable v = %h", v)
`print(data);
In this example, the macro ‘print() will expand to:
$display("variable v = %h", data);
The intent of this text substitution example is that all occurrences of the macro argument v will be substituted with the actual argument value, data. However, since the first occurrence of v is within quotes in the macro definition, Verilog does not substitute the first occurrence of v with data.
‘" allows macro argument substitution within strings
SystemVerilog allows argument substitution inside a macro text string by preceding the quotation marks that form the string with a grave accent ( ‘ ). The example below defines a text substitution macro that represents a complete $display statement. The string to be printed contains a %h format argument. The substituted text will contain a text string that prints a message, including the name and logic value of the argument to the macro. The %h within the string will be correctly interpreted as a format argument.
`define print(v) \
$display(‘"variable v = %h‘", v)
`print(data);
In this example, the macro ‘print() will expand to:
$display("variable data = %h", data);
In Verilog, quotation marks embedded within a string must be escaped using " so as to not affect the quotation marks of the outer string. The following Verilog example embeds quotation marks within a print message.
$display("variable \"data\" = %h", data);
‘\‘" allows an escaped quote in a macro text string containing argument substitution
When a string is part of a text substitution macro that contains variable substitution, it is not enough to use " to escape the embedded quotation marks. Instead, ‘\‘" must be used. For example:
`define print(v) \
$display(‘"variable ‘\‘"v‘\‘" = %h‘", v)
`print(data);
In this example, the macro ‘print() will expand to:
$display("variable \"data\" = %h", data);
3.2.2 Constructing identifier names from macros
Using Verilog ‘define, it is not possible to construct an identifier name by concatenating two or more text macros together. The problem is that there will always be a white space between each portion of the constructed identifier name.
‘‘ serves as a delimiter without a space in the macro text
SystemVerilog provides a way to delimit an identifier name without introducing a white space, using two consecutive grave accent
marks, i.e. ‘‘. This allows two or more names to be concatenated together to form a new name.
One application for ‘‘ is to simplify creating source code where a set of similar names are needed several times, and an array cannot be used. In the following example, a 2-state bit variable and a wand net need to be defined with similar names, and a continuous assignment of the variable to the net. The variable allows local procedural assignments, and the net allows wired logic assignments from multiple drivers, where one of the drivers is the 2-state variable: The bit type is discussed in more detail later in this chapter. In brief, the bit type is similar to the Verilog reg type, but bit variables only store 2-state values, whereas reg stores 4-state values.
In source code without text substitution, these declarations might be:
bit d00_bit; wand d00_net = d00_bit;
bit d01_bit; wand d01_net = d01_bit;
... // repeat 60 more times, for each bit
bit d62_bit; wand d62_net = d62_bit;
bit d63_bit; wand d63_net = d63_bit;
Using the SystemVerilog enhancements to ‘define, these declarations can be simplified as:
‘define TWO_STATE_NET(name) bit name‘‘_bit; \
wand name‘‘_net = name‘‘_bit;
‘TWO_STATE_NET(d00)
‘TWO_STATE_NET(d01)
...
‘TWO_STATE_NET(d62)
‘TWO_STATE_NET(d63)
‘‘ serves as a
delimiter without
a space in the
macro text
3.3 SystemVerilog variables
3.3.1 Object types and data types
Verilog data types
Verilog’s hardware types
The Verilog language has hardware-centric variable types and net types. These types have special simulation and synthesis semantics to represent the behavior of actual connections in a chip or system.
• The Verilog reg, integer and time variables have 4 logic values for each bit: 0, 1, Z and X.
• The Verilog wire, wor, wand, and other net types have 120 values for each bit (4-state logic plus multiple strength levels) and special wired logic resolution functions.
SystemVerilog data types
data declarations have a type and a data type
Verilog does not clearly distinguish between signal types, and the value set the signals can store or transfer. In Verilog, all nets and variables use 4-state values, so a clear distinction is not necessary. To provide more flexibility in variable and net types and the values that these types can store or transfer, the SystemVerilog standard defines that signals in a design have both a type and a data type.
“type” defines if data is a net or variable
Type indicates if the signal is a net or variable. SystemVerilog uses all the Verilog variable types, such as reg and integer, plus adds
several more variable types, such as byte and int. SystemVerilog does not add any extensions to the Verilog net types.
“data type” defines if data is 2-state or 4-state
Data type indicates the value system of the net or variable, which is 0 or 1 for 2-state data types, and 0, 1, Z or X for 4-state data types. The SystemVerilog keyword bit defines that an object is a 2-state data type. The SystemVerilog keyword logic defines that an object is a 4-state data type. In the SystemVerilog-2005 standard, variable types can be either 2-state or 4-state data types, where as net types can only be 4-state data types.
3.3.2 SystemVerilog 4-state variables
the Verilog reg type
The Verilog language uses the reg type as a general purpose variable for modeling hardware behavior in initial and always procedural blocks. The keyword reg is a misnomer that is often confusing to new users of the Verilog language. The term “reg”
would seem to imply a hardware “register”, built with some form of sequential logic flip-flops. In actuality, there is no correlation
whatsoever between using a reg variable and the hardware that will be inferred. It is the context in which the reg variable is used
that determines if the hardware represented is combinational logic or sequential logic.
the logic variable type replaces reg
SystemVerilog uses the more intuitive logic keyword to represent a general purpose, hardware-centric data type. Some example declarations using the logic type are:
logic resetN; // a 1-bit wide 4-state variable
logic [63:0] data; // a 64-bit wide variable
logic [0:7] array [0:255]; // an array of 8-bit variables
the logic keyword is a data type
The keyword logic is not actually a variable type, it is a data type, indicating the signal can have 4-state values. However, when the
logic keyword is used by itself, a variable is implied. A 4-state variable can be explicitly declared using the keyword pair var logic. For example:
var logic [63:0] addr; // a 64-bit wide variable
A Verilog net type defaults to being a 4-state logic data type. A net can also be explicitly declared as a 4-state data type using the logic keyword. For example:
wire logic [63:0] data; // a 64-bit wide net
Explicitly declaring the data type of nets and variables is discussed in more depth in section 3.3.4 on page 47.
Semantically, a variable of the logic data type is identical to the Verilog reg type. The two keywords are synonyms, and can be used interchangeably (except that the reg keyword cannot be paired with net type keywords, as discussed in section 3.3.4 on page 47). Like the Verilog reg variable type, a variable of the logic data type can store 4-state logic values (0, 1, Z and X), and can be defined as a vector of any width.
Because the keyword logic does not convey a false implication of the type of hardware represented, logic is a more intuitive keyword choice for describing hardware when 4-state logic is required. In the subsequent examples in this book, the logic type is used in place of the Verilog reg type (except when the example illustrates pure Verilog code, with no SystemVerilog enhancements).
3.3.3 SystemVerilog 2-state variables
SystemVerilog’s 2-state types
SystemVerilog adds several new 2-state types, suitable for modeling at more abstract levels than RTL, such as system level and
transaction level. These types include:
• bit — a 1-bit 2-state integer
• byte — an 8-bit 2-state integer, similar to a C char
• shortint — a 16-bit 2-state integer, similar to a C short
• int — a 32-bit 2-state integer, similar to a C int
• longint — a 64-bit 2-state integer, similar to a C longlong
Abstract modeling levels do not need 4-state values
Variables of the reg or logic data types are used for modeling hardware behavior in procedural blocks. These types store 4-state
logic values, 0, 1, Z and X. 4-state types are the preferred types for synthesizable RTL hardware models. The Z value is used to represent unconnected or tri-state design logic. The X value helps detect and isolate design errors. At higher levels of modeling, such as the system and transaction levels, logic values of Z and X are seldom required.
a 2-state bit variable can be used in place of reg or logic
SystemVerilog allows variables to be declared as a bit data type. Syntactically, a bit variable can be used any place reg or logic variables can be used. However, the bit data type is semantically different, in that it only stores 2-state values of 0 and 1. The bit
data type can be useful for modeling hardware at higher levels of abstraction.
Variables of the bit data type can be declared in the same way as reg and logic types. Declarations can be any vector width, from
1-bit wide to the maximum size supported by the software tool (the IEEE 1364 Verilog standard defines that all compliant software tools should support vector widths of at least \(2^{16}\) bits wide).
bit resetN; // a 1-bit wide 2-state variable
bit [63:0] data; // a 64-bit 2-state variable
bit [0:7] array [0:255]; // an array of 8-bit 2-state variables
the bit keyword is a data type
The keyword bit is not actually a variable type, it is a data type, indicating the variable can have 2-state values. However, when the
bit keyword is used by itself, a variable is implied. A 2-state variable can also be explicitly declared using the keyword pair var
bit. For example:
var bit [63:0] addr; // a 64-bit wide variable
Explicitly declaring the data type of variables is discussed in more depth in section 3.3.4 on page 47.
2-state types can be used to interface to C and C++ models
A primary usage for the C-like 2-state types, such as int and byte, is for modeling more abstract bus-functional models. At this level, it is not necessary for the model to represent detailed hardware such as tri-state busses and hardware resolution that can result in logic X values. Another key usage of these C-like types is for interfacing Verilog models to C or C++ models using SystemVerilog’s Direct Programming Interface (DPI). Using types that have a common representation in both languages makes it simple and efficient to pass data back and forth between the languages.
the int type can be used as a for-loop control variable
Another common usage of the int type can be as the loop-control variable in for loops. In synthesizable RTL models, the loop control variable is typically just a temporary variable that disappears in the synthesized gate-level representation of a design. As such, loop control variables do not need 4-state values. The int type works well as the control variable in for loops for both abstract models and synthesizable RTL models.
4-state types begin simulation with a logic X
The 4-state variables, such as reg, logic, and integer, default to beginning simulation with all bits at a logic X. These variables are considered uninitialized, and, therefore, at an unknown value until a first value is assigned to the variable (for example, by the design reset logic). 4-state variables can be defined to begin simulation with some other value using in-line initialization, but this is not synthesizable. In-line initialization is discussed more in section 3.8.
2-state types begin simulation with a logic 0
All 2-state date types begin simulation with a logic 0. Since 2-state types do not store an X value, they cannot represent an unitialized state. This is one of the reasons that it is preferable to use 4-state types to represent synthesizable RTL models.
X and Z values are mapped to 0 in 2-state types
It is legal to assign 4-state values to 2-state variables. For example, the value of a 4-state input to a model can be assigned to a 2-state bit type within the module. Any bits that have an X or Z value in the 4-state type will be translated to a logic 0 in the matching bit position of the 2-state variable.
a void type represents no storage
SystemVerilog adds a void type that indicates no storage. The void type can be used in tagged unions (see Chapter 5) and to define functions that do not return a value (see Chapter 6).
shortreal is equivalent to a C float
SystemVerilog also adds a shortreal variable type that compliments Verilog’s real type. shortreal stores a 32-bit single-precision
floating point, the same as a C float, whereas the Verilog real stores a double-precision variable, the same as a C double. The real and shortreal types are not synthesizable, but can be useful in abstract hardware models and in testbenches.
The verification enhancements in SystemVerilog add classes and other dynamic types for use in high-level testbenches. These types are not covered in this book.
3.3.4 Explicit and implicit variable and net data types
SystemVerilog has net and variable types, and 2-state or 4-state data types
In SystemVerilog terminology, variables and nets are types which can have either a 2-state or 4-state data type (In the 2005 System-Verilog standard, nets can only have a 4-state data type). A 4-state data type is represented with the keyword logic. A 2-state data type is represented with the keyword bit. When these 4-state or 2-state data types are used without explicitly specifying that the data type is a variable or net, an implicit variable is inferred.
logic [7:0] busA; // infers a variable that is a 4-state data type
bit [31:0] busB; // infers a variable that is a 2-state data type
The Verilog keywords integer and time are variables that are 4-state data types with predefined vector sizes. The SystemVerilog
keywords int, byte, shortint and longint are variables that are 2-state data types with predefined vector sizes.
SystemVerilog allows an optional var keyword to be specified before any of the data types. For example:
var logic [7:0] a; // 4-state 8-bit variable
var bit [31:0] b; // 2-state 32-bit variable
var int i; // 2-state 32-bit variable
“var” is short for “variable”
The var keyword (short for “variable”) documents that the object is a variable. The var keyword does not affect how a variable behaves in simulation or synthesis. Its usage is to help make code more self-documenting. This explicit documentation can help make code more readable and maintainable when variables are created from user-defined types. For example:
typedef enum bit {FALSE, TRUE} bool_t;
var bool_t c; // variable of user-defined type
A variable can also be declared using var without an explicit data type. In this case, the variable is assumed to be of the logic data
type.
var [7:0] d; // 4-state 8-bit variable
All Verilog net types (wire, uwire, wand, wor, tri, triand, trior, tri0, tri1, trireg, supply0 and supply1) are implicitly of a 4-state logic data type. There are no 2-state net types.
wire [31:0] busB; // declares a net type that is implicitly a a 4-state logic data type
Optionally, a net can be declared using both the net type and the logic data type:
wire logic [31:0] busC;
To prevent confusing combinations of keywords, SystemVerilog does not allow the keyword reg to be directly paired with any of the net type keywords.
wire reg [31:0] busD; // ILLEGAL keyword pair
3.3.5 Synthesis guidelines
2-state types synthesize the same as 4-state types
The 4-state logic type and the 2-state bit, byte, shortint, int, and longint types are synthesizable. Synthesis compilers treat 2- state and 4-state types the same way. The use of 2-state types primarily affects simulation.
synthesis ignores the default initial value of 2-state types
2-state types begin simulation with a default value of logic value of 0. Synthesis ignores this default initial value. The post-synthesis design realized by synthesis is not guaranteed to power up with zeros in the same way that pre-synthesis models using 2-state types will appear to power up.
Section 8.2 on page 219 presents additional modeling considerations regarding the default initial value of 2-state types.
3.4 Using 2-state types in RTL models
2-state types simulate differently than 4-state types. The initial value of 2-state types at simulation time 0 is different than 4-state types, and the propagation of ambiguous or faulty logic (typically indicated by a logic X in simulation) is different. This section iscusses some of the considerations designers should be aware of when 2-state types are used in RTL hardware models.
3.4.1 2-state type characteristics
SystemVerilog adds 2-state types
SystemVerilog adds several 2-state types to the Verilog language: bit (1-bit wide), byte (8-bits wide), shortint (16-bits wide), int (32-bits wide) and longint (64-bits wide). These 2-state types allow modeling designs at an abstract level, where tri-state values are seldom required, and where circuit conditions that can lead to unknown or unpredictable values—represented by a logic X— cannot occur.
mapping 4-state values to 2-state
SystemVerilog allows freely mixing 2-state and 4-state types within a module. Verilog is a loosely-typed language, and this characteristic is also true for SystemVerilog’s 2-state types. Thus, it is possible to assign a 4-state value to a 2-state type. When this occurs, the 4-state value is mapped to a 2-state value as shown in the following table:
3.4.2 2-state types versus 2-state simulation
tool-specific 2-state modes
Some software tools, simulators in particular, offer a 2-state mode for when the design models do not require the use of logic Z or X. These 2-state modes allow simulators to optimize simulation data structures and algorithms and can achieve faster simulation run times. SystemVerilog’s 2-state types permit software tools to make the same types of optimizations. However, SystemVerilog’s 2-state types have important advantages over 2-state simulation modes.
SystemVerilog standardizes mixing 2-state and 4-state types
The software tools that provide 2-state modes typically use an invocation option to specify using the 2-state mode algorithms. Invocation options are often globally applied to all files listed in the invocation command. This makes it difficult to have a mix of 2-state logic and 4-state logic. Some software tools provide a more flexible control, by allowing some modules to be compiled in 2-state mode, and others in the normal 4-state mode. These tools may also use tool-specific pragmas or other proprietary mechanisms to allow specific variables within a module to be specified as using 2-state or 4-state modes. All of these proprietary mechanisms are tool-specific, and differ from one software tool to another. System-Verilog’s 2-state types give the designer a standard way to specify which parts of a model should use 2-state logic and which parts should use 4-state logic.
SystemVerilog 2-state to 4-state mapping is standardized
With 2-state simulation modes, the algorithm for how to map a logic Z or logic X value to a 2-state value is proprietary to the software tool, and is not standardized. Different simulators can, and do, map values differently. For example, some commercial simulators will map a logic X to a 0, while others map a logic X to a 1. The different algorithms used by different software tools means that the simulation results of the same model may not be the same. System-Verilog’s 2-state types have a standard mapping algorithm, providing consistent results from all software tools.
SystemVerilog 2-state initialization is standardized
Another difference between 2-state modes and 2-state types involves the initialization of a variable to its 2-state value. The IEEE 1364 Verilog standard specifies that 4-state variables begin simulation with a logic X, indicating the variable has not been initialized. The first time the 4-state variable is initialized to a 0 or 1 will cause a simulation event, which can trigger other activity in the design. Whether or not the event propagates to other parts of the design depends in part on nondeterministic event ordering. Most of the proprietary 2-state mode algorithms will change the initial value of 4-state variables to be a logic 0 instead of a logic X, but there is no standard on when the initialization occurs. Some simulators with 2-state modes will set the initial value of the variable without causing a simulation event. Other simulators will cause a simulation event at time zero as the initial value is changed from X to 0, which may propagate to other constructs sensitive to negative edge transitions. The differences in these proprietary 2-state mode algorithms can lead to differences in simulation results between different software tools. The SystemVerilog 2-state variables are specifically defined to begin simulation with a logic value of 0 without causing a simulation event. This standard rule ensures consistent behavior in all software tools.
SystemVerilog 2-state is standardized
The Verilog casez and casex decision statements can be affected by 2-state simulation modes. The casez statement treats a logic Z as a don’t care value instead of high-impedance. The casex statement treats both a logic X and a logic Z as don’t care. When a proprietary 2-state mode algorithm is used, there is no standard to define how casez and casex statements will be affected. Furthermore, since these simulation modes only change the 4-state behavior within one particular tool, some other tool that might not have a 2-state mode might interpret the behavior of the same model differently. SystemVerilog’s standard 2-state types have defined semantics that provide deterministic behavior with all software tools.
3.4.3 Using 2-state types with case statements
At the abstract RTL level of modeling, logic X is often used as a flag within a model to show an unexpected condition. For example, a common modeling style with Verilog case statements is to make the default branch assign outputs to a logic X, as illustrated in the following code fragment:
case (State)
RESET: Next = WAITE;
WAITE: Next = LOAD;
LOAD: Next = DONE;
DONE: Next = WAITE;
default: Next = 4’bx; // unknown state
endcase
The default assignment of a logic X serves two purposes. Synthesis treats the default logic X assignment as a special flag, indicating that, for any condition not covered by the other case selection items, the output value is “don’t care”. Synthesis will optimize the decode logic for the case selection items, without concern for what is decoded for case expression values that would fall into the default branch. This can provide better optimizations for the explicitly defined case selection items, but at the expense of indeterminate results, should an undefined case expression value occur.
Within simulation, the default assignment of logic X serves as an obvious run-time error, should an unexpected case expression value occur. This can help trap design errors in the RTL models. However, this advantage is lost after synthesis, as the post-synthesis model will not output logic X values for unexpected case expression values.
Assigning a logic X to a 2-state variable is legal. However, the assignment of a logic X to a variable will result in the variable having
a value of 0 instead of an X. If the State or Next variables are 2-state types, and if a value of 0 is a legitimate value for State or Next, then the advantage of using an X assignment to trap design errors at the RTL level is lost. The default X assignment will still
allow synthesis compilers to optimize the decode logic for the case selection items. This means that the post-synthesis behavior of the design will not be the same, because the optimized decoding will probably not result in a 0 for undefined case expression values.
3.5 Relaxation of type rules
Verilog restricts usage of variables and nets
In Verilog, there are strict semantic restrictions regarding where variable types such as reg can be used, and where net types such as wire can be used. When to use reg and when to use wire is based entirely on the context of how the signal is used within the model. The general rule of thumb is that a variable must be used when modeling using initial and always procedural blocks, and a net must be used when modeling using continuous assignments, module instances or primitive instances.
These restrictions on type usage are often frustrating to engineers who are first learning the Verilog language. The restrictions also make it difficult to evolve a model from abstract system level to RTL to gate level because, as the context of the model changes, the type declarations may also have to be changed.
SystemVerilog relaxes restrictions on using variables
SystemVerilog greatly simplifies determining the proper type to use in a model, by relaxing the rules of where variables can be used. With SystemVerilog, a variable can receive a value in any one of the following ways, but no more than one of the following ways:
• Be assigned a value from any number of initial or always procedural blocks (the same rule as in Verilog).
• Be assigned a value from a single always_comb, always_ff or always_latch procedural block. These SystemVerilog procedural
blocks are discussed in Chapter 6.
• Be assigned a value from a single continuous assignment statement.
• Receive a value from a single module or primitive output or inout port.
most signals can be declared as logic or bit
These relaxed rules for using variables allow most signals in a model to be declared as a variable. It is not necessary to first determine the context in which that signal will be used. The type of the signal does not need to be changed as the model evolves from system level to RTL to gate level.
The following simple example illustrates the use of variables under these relaxed type rules.
Example 3-1: Relaxed usage of variables
module compare (output logic lt, eq, gt,
input logic [63:0] a, b );
always @(a, b)
if (a < b) lt = 1'b1; // procedural assignments
else lt = 1'b0;
assign gt = (a > b); // continuous assignments
comparator u1 (eq, a, b); // module instance
endmodule
module comparator (output logic eq,
input [63:0] a, b);
always @(a, b)
eq = (a==b);
endmodule
Restrictions on variables can prevent design errors
NOTE: Variables cannot be driven by multiple sources.
SystemVerilog restrictions on using variables
It is important to note that though SystemVerilog allows variables to be used in places where Verilog does not, SystemVerilog does still have some restrictions on the usage of variables.
SystemVerilog makes it an error to have multiple output ports or multiple continuous assignments write to the same variable, or to combine procedural assignments with continuous assignments or output drivers on the same variable.
The reason for these restrictions is that variables do not have builtin resolution functionality to resolve a final value when two or
more devices drive the same output. Only the Verilog net types, such as wire, wand (wire-and) and wor (wire-or), have built-in resolution functions to resolve multi-driver logic. (The Verilog-2005 standard also has a uwire net type, which restricts its usage to a single driver, the same as with variables.)
Example 3-2: Illegal use of variables
module add_and_increment (output logic [63:0] sum,
output logic carry,
input logic [63:0] a, b );
always @(a, b)
sum = a + b; // procedural assignment to sum
assign sum = sum + 1; // ERROR! sum is already being
// assigned a value
look_ahead i1 (carry, a, b); // module instance drives carry
overflow_check i2 (carry, a, b); // ERROR! 2nd driver of carry
endmodule
module look_ahead (output wire carry,
input logic [63:0] a, b);
...
endmodule
module overflow_check (output wire carry,
input logic [63:0] a, b);
...
endmodule
TIP: Use variables for single-driver logic, and use nets for multidriver logic.
SystemVerilog’s restriction that variables cannot receive values from multiple sources can help prevent design errors. Wherever a
signal in a design should only have a single source, a variable can be used. The single source can be procedural block assignments, a single continuous assignment, or a single output/inout port of a module or primitive. Should a second source inadvertently be con-nected to the same signal, it will be detected as an error, because each variable can only have a single source.
SystemVerilog does permit a variable to be written to by multiple always procedural blocks, which can be considered a form of multiple sources. This condition must be allowed for backward compatibility with the Verilog language. Chapter 6 introduces three new types of procedural blocks: always_comb, always_latch and always_ff. These new procedural blocks have the restriction that a variable can only be assigned from one procedural block. This further enforces the checking that a signal declared as a variable only has a single source.
Only nets can have multiple sources, such as multiple continuous assignments and/or connections to multiple output ports of module or primitive instances. Therefore, a signal in a design such as a data bus or address bus that can be driven from several devices should be declared as a Verilog net type, such as wire. Bi-directional module ports, which can be used as both an input and an output, must also be declared as a net type.
It is also illegal to write to an automatic variable from a continuous assignment or a module output. Only static variables can be continuously assigned or connected to an output port. Static variables are required because the variable must be present throughout simulation in order to continuously write to it. Automatic variables do not necessarily exist the entire time simulation is running.
3.6 Signed and unsigned modifiers
Verilog-1995 signed types
The first IEEE Verilog standard, Verilog-1995, had just one signed type, declared with the keyword integer. This type has a fixed
size of 32 bits in most, if not all, software tools that support Verilog. Because of this, and some limitations of literal numbers, Verilog-1995 was limited to doing signed operations on just 32-bit wide vectors. Signed operations could be performed on other vector sizes by manually testing and manipulating a sign bit (the way it is done in actual hardware), but this required many lines of extra code, and could introduce coding errors that are difficult to detect.
Verilog signed types
The IEEE Verilog-2001 standard added several significant enhancements to allow signed arithmetic operations on any type and with any vector size. The enhancement that affects types is the ability to declare any type as signed. This modifier overrides the default definition of unsigned types in Verilog. For example:
reg [63:0] u; // unsigned 64-bit variable
reg signed [63:0] s; // signed 64-bit variable
SystemVerilog signed and unsigned types
SystemVerilog adds new types that are signed by default. These signed types are: byte, shortint, int, and longint. SystemVerilog
provides a mechanism to explicitly override the signed behavior of these new types, using the unsigned keyword.
int s_int; // signed 32-bit variable
int unsigned u_int; // unsigned 32-bit variable
NOTE: SystemVerilog’s signed declaration is not the same as C’s.
The C language allows the signed or unsigned keyword to be specified before or after the type keyword.
unsigned int u1; /* legal C declaration */
int unsigned u2; /* legal C declaration */
Verilog places the signed keyword (Verilog does not have an unsigned keyword) after the type declaration, as in:
reg signed [31:0] s; // Verilog declaration
SystemVerilog also only allows the signed or unsigned keyword to be specified after the type keyword. This is consistent with Verilog, but different than C.
int unsigned u; // SystemVerilog declaration
3.7 Static and automatic variables
Verilog-1995 variables are static
In the Verilog-1995 standard, all variables are static, with the expectation that these variables are for modeling hardware, which is also static in nature.
Verilog-2001 has automatic variables in tasks and functions
The Verilog-2001 standard added the ability to define variables in a task or function as automatic, meaning that the variable storage is dynamically allocated by the software tool when required, and deallocated when no longer needed. Automatic variables—also referred to as dynamic variables—are primarily intended for representing verification routines in a testbench, or in abstract system-level, transaction-level or bus-functional models. One usage of automatic variables is for coding a re-entrant task, so that the task can be called while a previous call of the task is still running.
Automatic variables also allow coding recursive function calls, where a function calls itself. Each time a task or function with automatic variables is called, new variable storage is created. When the call exits, the storage is destroyed. The following example illustrates a balance adder that adds the elements of an array together. The low address and high address of the array elements to be added are passed in as arguments. The function then recursively calls itself to add the array elements. In this example, the arguments lo and hi are automatic, as well as the internal variable mid. Therefore, each recursive call allocates new variables for that specific call.
function automatic int b_add (int lo, hi);
int mid = (lo + hi + 1) >> 1;
if (lo + 1 != hi)
return(b_add(lo,(mid-1)) + b_add(mid,hi));
else
return(array[lo] + array[hi]);
endfunction
In Verilog, automatic variables are declared by declaring the entire task or function as automatic. All variables in an automatic task or function are dynamic.
SystemVerilog adds static and automatic variable declarations
SystemVerilog extends the ability to declare static and automatic variables. SystemVerilog adds a static keyword, and allows any
variable to be explicitly declared as either static or automatic. This declaration is part of the variable declaration, and can appear
within tasks, functions, begin...end blocks, or fork...join blocks. Note that variables declared at the module level cannot be explicitly declared as static or automatic. At the module level, all variables are static.
The following code fragment illustrates explicit automatic declarations in a static function:
function int count_ones (input [31:0] data);
automatic logic [31:0] count = 0;
automatic logic [31:0] temp = data;
for (int i=0; i<=32; i++) begin
if (temp[0]) count++;
temp >>= 1;
end
return count;
endfunction
The next example illustrates an explicit static variable in an automatic task. Automatic tasks are often used in verification to allow
test code to call a task while a previous call to the task is still executing. This example checks a value for errors, and increments an
error count each time an error is detected. If the error_count variable were automatic as is the rest of the task, it would be recreated each time the task was called, and only hold the error count for that call of the task. As a static variable, however, error_count retains its value from one call of the task to the next, and can thereby keep a running total of all errors.
typedef struct packed {...} packet_t;
task automatic check_results
(input packet_t sent, received);
output int total_errors);
static int error_count;
...
if (sent !== received) error_count++;
total_errors = error_count;
endtask
backward compatibility
The defaults for storage in SystemVerilog are backward compatible with Verilog. In modules, begin...end blocks, fork...join blocks, and non-automatic tasks and functions, all storage defaults to static, unless explicitly declared as automatic. This default behavior is the same as the static storage in Verilog modules, begin...end or fork...join blocks and non-automatic tasks and functions. If a task or function is declared as automatic, the default storage for all variables will be automatic, unless explicitly declared as static. This default behavior is the same as with Verilog, where all storage in an automatic task or function is automatic.
3.7.1 Static and automatic variable initialization
Verilog variable in-line variable initialization
Verilog only permits in-line variable initialization for variables declared at the module level. Variables declared in tasks, functions
and begin...end or fork...join blocks cannot have an initial value specified as part of the variable declaration.
initializing automatic variables
SystemVerilog extends Verilog to allow variables declared within tasks and functions to be declared with in-line initial values.
A variable declared in a non-automatic task or function will be static by default. An in-line initial value will be assigned one time, before the start of simulation. Calls to the task or function will not re-initialize the variable.
NOTE: Initializing static variables in a task or function is not considered synthesizable, and may not be supported in some tools.
static variables are only initialized once
The following example will not work correctly. The count_ones function is static, and therefore all storage within the function is
also static, unless expressly declared as automatic. In this example, the variable count will have an initial value of 0 the first time
the function is called. However, it will not be re-initialized the next time it is called. Instead, the static variable will retain its value from the previous call, resulting in an erroneous count. The static variable temp will have a value of 0 the first time the function is called, rather than the value of data. This is because in-line initialization takes place prior to time zero, and not when the function is called.
function int count_ones (input [31:0] data);
logic [31:0] count = 0; // initialized once
logic [31:0] temp = data; // initialized once
for (int i=0; i<=32; i++) begin
if (temp[0]) count++;
temp >>= 1;
end
return(count);
endfunction
automatic variables are initialized each call
A variable explicitly declared as automatic in a non-automatic task or function will be dynamically created each time the task or
function is entered, and only exists until the task or function exits. An in-line initial value will be assigned each time the task or function is called. The following version of the count_ones function will work correctly, because the automatic variables count and
temp are initialized each time the function is called.
function int count_ones (input [31:0] data);
automatic logic [31:0] count = 0;
automatic logic [31:0] temp = data;
for (int i=0; i<=32; i++) begin
if (temp[0]) count++;
temp >>= 1;
end
return(count);
endfunction
A variable declared in an automatic task or function will be automatic by default. Storage for the variable will be dynamically created each time the task or function is entered, and destroyed each time the task or function exits. An in-line initial value will be
assigned each time the task or function is entered and new storage is created.
3.7.2 Synthesis guidelines for automatic variables
The dynamic storage of automatic variables can be used both in verification testbenches and to represent hardware models. To be synthesized in a hardware model, the automatic variables should only be used to represent temporary storage that does not propagate outside of the task, function or procedural block.
NOTE: Static variable initialization is not synthesizable. Automatic variable initialization is synthesizable.
Initialization of static variables is not synthesizable, and should be reserved for usage in testbench code and abstract bus functional models.
In-line initialization of automatic variables is synthesizable. The count_ones function example listed earlier in this chapter, in section 3.7, meets these synthesis criteria. The automatic variables count and temp are only used within the function, and the values of the variables are only used by the current call to the function.
In-line initialization of variables declared with the const qualifier is also synthesizable. Section 3.10 on page 71 covers const declarations.
3.7.3 Guidelines for using static and automatic variables
The following guidelines will aid in the decision on when to use static variables and when to use automatic variables.
• In an always or initial block, use static variables if there is no in-line initialization, and automatic variables if there is an inline
initialization. Using automatic variables with in-line initialization will give the most intuitive behavior, because the variable
will be re-initialized each time the block is re-executed.
• If a task or function is to be re-entrant, it should be automatic. The variables also ought to be automatic, unless there is a specific reason for keeping the value from one call to the next. As a simple example, a variable that keeps a count of the number of times an automatic task or function is called would need to be static.
• If a task or function represents the behavior of a single piece of hardware, and therefore is not re-entrant, then it should be
declared as static, and all variables within the task or function should be static.
3.8 Deterministic variable initialization
3.8.1 Initialization determinism
Verilog-1995 variable initialization
In the original Verilog language, which was standardized in 1995, variables could not be initialized at the time of declaration, as can be done in C. Instead, a separate initial procedural block was required to set the initial value of variables. For example:
integer i; // declare a variable named i
integer j; // declare a variable named j
initial
i = 5; // initialize i to 5
initial
j = i; // initialize j to the value of i
Verilog-1995 initialization can be nondeterministic
The Verilog standard explicitly states that the order in which a software tool executes multiple initial procedural blocks is nondeterministic. Thus, in the preceding example it cannot be determined whether j will be assigned the value of i before i is initialized to 5 or after i is initialized. If, in the preceding example, the intent is that i is assigned a value of 5 first, and then j is assigned the value of i, the only deterministic way to model the initialization is to group both assignments into a single initial procedural block with a begin...end block. Statements within begin...end blocks execute in sequence, giving the user control the order in which the statements are executed.
integer i; // declare a variable named i
integer j; // declare a variable named j
initial begin
i = 5; // initialize i to 5
j = i; // initialize j to the value of i
end
Verilog-2001 variable initialization
The Verilog-2001 standard added a convenient short cut for initializing variables, following the C language syntax of specifying a
variable’s initial value as part of the variable declaration. Using Verilog, the preceding example can be shortened to:
integer i = 5; // declare and initialize i
integer j = i; // declare and initialize j
Verilog initialization is nondeterministic
Verilog defines the semantics for in-line variable initialization to be exactly the same as if the initial value had been assigned in an initial procedural block. This means that in-line initialization will occur in a nondeterministic order, in conjunction with the execution of events in other initial procedural blocks and always procedural blocks that execute at simulation time zero.
This nondeterministic behavior can lead to simulation results that might not be expected when reading the Verilog code, as in the following example:
integer i = 5; // declare and initialize i
integer j; // declare a variable named j
initial
j = i; // initialize j to the value of i
In this example, it would seem intuitive to expect that i would be initialized first, and so j would be initialized to a value of 5. The
nondeterministic event ordering specified in the Verilog standard, however, does not guarantee this. It is within the specification of the Verilog standard for j to be assigned the value of i before i has been initialized, which would mean j would receive a value of X instead of 5.
SystemVerilog initialization order
SystemVerilog in-line initialization is before time zero
The SystemVerilog standard enhances the semantics for in-line variable initialization. SystemVerilog defines that all in-line initial
values will be evaluated prior to the execution of any events at the start of simulation time zero. This guarantees that when initial
or always procedural blocks read variables with in-line initialization, the initialized value will be read. This deterministic behavior
removes the ambiguity that can arise in the Verilog standard.
NOTE: SystemVerilog in-line variable initialization does not cause a simulation event.
Verilog in-line initialization may cause an event
There is an important difference between Verilog semantics and SystemVerilog semantics for in-line variable initialization. Under
Verilog semantic rules, in-line variable initialization will be executed during simulation time zero. This means a simulation event
will occur if the initial value assigned to the variable is different than its current value. Note, however, that the current value of the variable cannot be known with certainty, because the in-line initialization occurs in a nondeterministic order with other initial assignments—in-line or procedural—that are executed at time zero. Thus, with Verilog semantics, in-line variable initialization may or may not cause in-line initialization simulation events to propagate at simulation time zero.
SystemVerilog initialization does not cause an event
SystemVerilog semantics change the behavior of in-line variable initialization. With SystemVerilog, in-line variable initialization
occurs prior to simulation time zero. Therefore, the initialization will never cause a simulation event within simulation.
SystemVerilog initialization is backward compatible
The simulation results using the enhanced SystemVerilog semantics are entirely within the allowed, but nondeterministic, results of the Verilog initialization semantics. Consider the following example:
logic resetN = 0; // declare & initialize reset
always @(posedge clock, negedge resetN)
if (!resetN) count <= 0; // active low reset
else count <= count + 1;
Verilog in-line initialization is nondeterministic
Using the Verilog nondeterministic semantics for in-line variable initialization, two different simulation results can occur:
• A simulator could activate the always procedural block first, prior to initializing the resetN variable. The always procedural
block will then be actively watching for the next positive transition event on clock or negative transition event on resetN. Then, still at simulation time zero, when resetN is initialized to 0, which results in an X to 0 transition, the activated always procedural
block will sense the event, and reset the counter at simulation time zero.
• Alternatively, under Verilog semantics, a simulator could execute the initialization of resetN before the always procedural block
is activated. Then, still at simulation time zero, when the always procedural block is activated, it will become sensitive to the next
positive transition event on clock or negative transition event on resetN. Since the initialization of resetN has already occurred
in the event ordering, the counter will not trigger at time zero, but instead wait until the next positive edge of clock or negative
edge of resetN.
SystemVerilog in-line initialization is deterministic
The in-line initialization rules defined in the Verilog standard permit either of the two event orders described above. SystemVerilog removes this non-determinism. SystemVerilog ensures that in-line initialization will occur first, meaning only the second scenario can occur for the example shown above. This behavior is fully backward compatible with the Verilog standard, but is deterministic instead of nondeterministic.
3.8.2 Initializing sequential logic asynchronous inputs
Verilog’s nondeterministic order for variable initialization can result in nondeterministic simulation behavior for asynchronous
reset or preset logic in sequential models. This nondeterminism can affect resets or presets that are applied at the beginning of simulation.
Example 3-3: Applying reset at simulation time zero with 2-state types
module counter (input wire clock, resetN,
output logic [15:0] count);
always @(posedge clock, negedge resetN)
if (!resetN) count <= 0; // active low reset
else count <= count + 1;
endmodule
module test;
wire [15:0] count;
bit clock;
bit resetN = 1; // initialize reset to inactive value
counter dut (clock, resetN, count);
always #10 clock = ~clock;
initial begin
resetN = 0; // assert active-low reset at time 0
#2 resetN = 1; // de-assert reset before posedge of clock
$display("\n count=%0d (expect 0)\n", count);
#1 $finish;
end
endmodule
In the example above, the counter has an asynchronous reset input. The reset is active low, meaning the counter should reset the moment resetN transitions to 0. In order to reset the counter at simulation time zero, the resetN input must transition to logic 0. If resetN is declared as a 2-state type such as bit, as in the testbench example above, its initial value by default is a logic 0. The first test in the testbench is to assert reset by setting resetN to 0. However, since resetN is a 2-state data type, its default initial value is 0. The first test will not cause a simulation event on resetN, and therefore the counter model sensitivity list will not sense a change on resetN and trigger the procedural block to reset the counter.
To ensure that a change on resetN occurs when resetN is set to 0, resetN is declared with an in-line initialization to logic 1, the inactive state of reset.
bit resetN = 1; // initialize reset
Following Verilog semantic rules, this in-line initialization is executed during simulation time zero, in a nondeterministic order with other assignments executed at time zero. In the preceding example, two event orders are possible:
• The in-line initialization could execute first, setting resetN to 1, followed by the procedural assignment setting resetN to 0. A
transition to 0 will occur, and at the end of time step 0, resetN will be 0.
• The procedural assignment could execute first, setting resetN to 0 (a 2-state type is already a 0), followed by the in-line initialization setting resetN to 1. No transition to 0 will occur, and at the end of time step 0, resetN will be 1.
SystemVerilog removes this non-determinism. With SystemVerilog, in-line initialization will take place before simulation time
zero. In the example shown above, resetN will always be initialized to 1 first, and then the procedural assignment will execute, setting resetN to 0. A transition from 1 to 0 will occur every time, in every software tool. At the end of time step 0, resetN will be 0.
TIP: Testbenches should initialize variables to their inactive state.
ensuring events at time zero
The deterministic behavior of SystemVerilog in-line variable initialization makes it possible to guarantee the generation of events at simulation time zero. If the variable is initialized using in-line initialization to its inactive state, and then set to its active state using an initial or always procedural block, SystemVerilog semantics ensure that the in-line initialization will occur first, followed by the procedural initial assignment.
In the preceding example, the declaration and initialization of resetN would likely be part of a testbench, and the always proce-dural block representing a counter would be part of an RTL model. Whether in the same module or in separate modules, SystemVerilog’s deterministic behavior for in-line variable initialization ensures that a simulation event will occur at time zero, if a variable is initialized to its inactive state using in-line initialization, and then changed to its active level at time zero using a procedural assignment. Verilog’s nondeterministic ordering of in-line initialization versus procedural initialization does not guarantee that the desired events will occur at simulation time zero.
3.9 Type casting
Verilog is loosely typed
Verilog is a loosely typed language that allows a value of one type to be assigned to a variable or net of a different type. When the
assignment is made, the value is converted to the new type, following rules defined as part of the Verilog standard.
casting is different than loosely typed
SystemVerilog adds the ability to cast a value to a different type. Type casting is different than converting a value during an assignment. With type casting, a value can be converted to a new type within an expression, without any assignment being made.
Verilog does not have type casting
The Verilog 1995 standard did not provide a way to cast a value to a different type. Verilog-2001 added a limited cast capability that can convert signed values to unsigned, and unsigned values to signed. This conversion is done using the system functions $signed
and $unsigned
.
3.9.1 Static (compile time) casting
SystemVerilog adds a cast operator
SystemVerilog adds a cast operator to the Verilog language. This operator can be used to cast a value from one type to another, similar to the C language. SystemVerilog’s cast operator goes beyond C, however, in that a vector can be cast to a different size, and signed values can be cast to unsigned or vice versa.
To be compatible with the existing Verilog language, the syntax of SystemVerilog’s cast operator is different than C’s.
type casting
<type>’(<expression>) — casts a value to any type, including user-defined types. For example:
7+ int’(2.0 * 3.0); // cast result of (2.0 * 3.0) to int, then add to 7
size casting
<size>’(<expression>) — casts a value to any vector size. For example:
logic [15:0] a, b, y;
y = a + b**16'(2); // cast literal value 2 to be 16 bits wide
sign casting
<sign>’(<expression>) — casts a value to signed or unsigned. For example:
shortint a, b;
int y;
y = y - signed'({a,b}); // cast concatenation result to a signed value
Static casting and error checking
static casting does not have run-time checking
The static cast operation is a compile-time cast. The expression to be cast will always be converted during run time, without any
checking that the expression to be cast falls within the legal range of the type to which the value is cast. In the following example, a static cast is used to increment the value of an enumerated variable by 1. The static cast operator does not check that the result of state + 1 is a legal value for the next_state enumerated type. Assigning an out of range value to next_state using a static cast
will not result in a compile-time or run-time error. Therefore, care must be taken not to cause an illegal value to be assigned to the next_state variable.
typedef enum {S1, S2, S3} states_t;
states_t state, next_state;
always_comb begin
if (state != S3)
next_state = states_t'(state + 1);
else
next_state = S1;
end
3.9.2 Dynamic casting
compile-time versus dynamic casting
The static cast operation described above is a compile-time cast. The cast will always be performed, without checking the validity of the result. When stronger checking is desired, SystemVerilog provides a new system function, $cast
, that performs dynamic, runtime checking on the value to be cast.
$cast
system function
The $cast
system function takes two arguments, a destination variable and a source variable. The syntax is:
$cast( dest_var, source_exp );
For example:
int radius, area;
always @(posedge clock)
$cast(area, 3.154 * radius ** 2);
// result of cast operation is cast to the type of area
invalid casts
$cast
attempts to assign the source expression to the destination variable. If the assignment is invalid, a run-time error is reported, and the destination variable is left unchanged. Some examples that would result in an invalid cast are:
• Casting a real to an int, when the value of the real number is too large to be represented as an int (as in the example, above).
• Casting a value to an enumerated type, when the value does not exist in the legal set of values in the enumerated type list, as in
the example, that follows.
typedef enum {S1, S2, S3} states_t;
states_t state, next_state;
always_latch begin
$cast(next_state, state + 1);
end
$cast
can be called as a task
$cast
can be called as a task as in the example above. When called as a task, a runtime error is reported if the cast fails, and the destination variable is not changed. In the example above, not changing the next_state variable will result in latched functionality.
$cast
can return a status flag
$cast
can be called as a system function. The function returns a status flag indicating whether or not the cast was successful. If the cast is successful, $cast
returns 1. If the cast fails, the $cast
function returns 0, and does not change the destination variable. When called as a function, no runtime error is reported.
typedef enum {S1, S2, S3} states_t;
states_t state, next_state;
int status;
always_comb begin
status = $cast(next_state, state + 1);
if (status == 0) // if cast did not succeed...
next_state = S1;
end
Note that the $cast
function cannot be used with operators that directly modify the source expression, such as ++ or +=.
$cast(next_state, ++state); // ILLEGAL
A primary usage for $cast
is to assign expression results to enumerated type variables, which are strongly typed variables. Additional examples of using $cast
are presented in section 4.2 on page 79, on enumerated types.
3.9.3 Synthesis guidelines
TIP: Use the compile-time cast operator for synthesis.
The static, compile-time cast operator is synthesizable. The dynamic $cast
system function might not be supported by synthesis
compilers. At the time this book was written, the IEEE 1364.1Verilog RTL synthesis standards group had not yet defined the synthesis guidelines for SystemVerilog. As a general rule, however, system tasks and system functions are not considered synthesizable constructs. A safe coding style for synthesis is to use the static cast operator for casting values.
3.10 Constants
Verilog constants
Verilog provides three types of constants: parameter, specparam and localparam. In brief:
• parameter is a constant for which the value can be redefined during elaboration using defparam or in-line parameter redefinition.
• specparam is a constant that can be redefined at elaboration time from SDF files.
• localparam is an elaboration-time constant that cannot be directly redefined, but for which the value can be based on other
constants.
it is illegal to assign constants a hierarchical reference
These Verilog constants all receive their final value at elaboration time. Elaboration is essentially the process of a software tool building the hierarchy of the design represented by module instances. Some software tools have separate compile and elaboration phases. Other tools combine compilation and elaboration into a single process. Because the design hierarchy may not yet be fully resolved during elaboration, it is illegal to assign a parameter, specparam or localparam constant a value that is derived from elsewhere in the design hierarchy.
constants are not allowed in automatic tasks and functions
Verilog also restricts the declaration of the parameter, specparam and localparam constants to modules, static tasks, and static functions. It is illegal to declare one of these constants in an automatic task or function, or in a begin...end or fork...join block.
the C-like const declaration
SystemVerilog adds the ability to declare any variable as a constant, using the const keyword. The const form of a constant is not
assigned its value until after elaboration is complete. Because a const constant receives its value after elaboration, a const constant can:
• Be declared in dynamic contexts such as automatic tasks and functions.
• Be assigned a value of a net or variable instead of a constant expression.
• Be assigned a value of an object that is defined elsewhere in the design hierarchy.
The declaration of a const constant must include a type. Any of the Verilog or SystemVerilog variable types can be specified as a
const constant, including enumerated types and user-defined types.
const logic [23:0] C1 = 7; // 24-bit constant
const int C2 = 15; // 32-bit constant
const real C3 = 3.14; // real constant
const C4 = 5; // ERROR, no type
const can be used in automatic tasks and functions
A const constant is essentially a variable that can only be initialized. Because the const form of a constant receives its value at
run-time instead of elaboration, a const constant can be declared in an automatic task or function, as well as in modules or static
tasks and functions. Variables declared in a begin...end or fork...join block can also be declared as a const constant.
task automatic C;
const int N = 5; // N is a constant
...
endtask
3.11 Summary
This chapter introduced and discussed the powerful compilationunit declaration scope. The proper use of compilation-unit scope
declarations can make it easier to model functionality in a more concise manner. A primary usage of compilation-unit scope declarations is to define new types using typedef.
SystemVerilog enhances the ability to specify logic values, making it easier to assign values that easily scale to any vector size.
Enhancements to the ‘define text substitution provide new capabilities to macros within Verilog models and testbenches.
SystemVerilog also adds a number of new 2-state variables to the Verilog language: bit, byte, shortint, int, and longint. These variable types enable modeling designs at a higher level of abstraction, using 2-state values. The semantic rules for 2-state values
are well defined, so that all software tools will interpret and execute Verilog models using 2-state logic in the same way. A new shortreal type and a logic type are also added. The initialization of variables is enhanced, so as to reduce ambiguities that exist
in the Verilog standard. This also helps ensure that all types of software tools will interpret SystemVerilog models in the same way. SystemVerilog also enhances the ability to declare variables that are static or automatic (dynamic) in various levels of design hierarchy. These enhancements include the ability to declare constants in begin...end blocks and in automatic tasks and functions.
The next chapter continues the topic on SystemVerilog types, covering user-defined types and enumerated types