While Verilog-1995 provided basic data types like the four-state reg and wire, SystemVerilog introduces a rich set of enhanced data structures specifically designed to help both hardware designers and verification engineers write more abstract, readable, and robust code.
Here is a breakdown of the essential built-in and custom data types you need to know when building your verification environment.
1. Built-in Data Types: The logic Type and 2-State Variables
One of the most confusing aspects of classic Verilog is knowing when to use a reg versus a wire. SystemVerilog simplifies this by introducing the logic type, which is an improved version of reg. A logic variable can be driven by continuous assignments, gates, and modules, making it incredibly versatile. The only exception is that it cannot be driven by multiple drivers, such as a bidirectional bus, which still requires a net type like wire.
SystemVerilog also introduces several two-state data types (0 and 1) to improve simulator performance and reduce memory usage.
- bit: A single-bit, unsigned two-state variable.
- byte, shortint, int, longint: Signed variables of 8, 16, 32, and 64 bits, respectively.
A quick warning for hardware designers: Because types like byte are signed by default, an 8-bit byte can only count up to 127, not 255. If you need an unsigned value, you must explicitly declare it, such as byte unsigned. Additionally, when hardware drives an unknown value (X or Z) into a two-state variable, it gets converted to a 0 or 1, so always check for unknown values at the source using the $isunknown operator.
2. Creating New Types with typedef
In Verilog, engineers often relied on text macros (#define) to create custom operand widths or types, which was merely text substitution. SystemVerilog introduces the typedef statement, allowing you to create true user-defined types.
For example, most values in a testbench (like transaction lengths or counts) are positive integers. You can easily define a reusable 32-bit unsigned integer type and use it throughout your testbench:
typedef int unsigned uint; // 32-bit unsigned 2-state
By convention, user-defined types often use an _t suffix to make them easily identifiable.
3. Enumerated Types (enum)
An enumerated type creates a strong variable type that is limited to a specific set of named constants, such as state machine values or instruction opcodes. Using names like READ, WRITE, or IDLE makes your code significantly easier to maintain and read compared to using literal numbers.
typedef enum {INIT, DECODE, IDLE} fsmstate_e;
fsmstate_e pstate, nstate;
By default, enumerated types are stored as int and start at 0, but you can assign your own values to the constants. SystemVerilog also provides built-in functions to navigate enumerated types, such as first(), last(), next(), and prev(). Because enums are strongly typed, you cannot directly assign an integer to an enumerated variable without explicitly using the $cast function to ensure the value is valid.
4. Constants (const)
While Verilog relied on global macros for constants, SystemVerilog introduces the const modifier. This allows you to create variables that are initialized at their declaration but cannot be written to or altered by procedural code. This gives you local, strongly-typed constants without the global conflict risks of a macro.
5. Strings
If you have ever suffered through using a Verilog reg variable to hold characters, SystemVerilog’s string type is a massive relief. SystemVerilog strings are variable-length, use dynamic memory allocation, and do not use a null character (\0) at the end.
The language provides several built-in methods to manipulate string data easily:
toupper()andtolower()to change case.getc(N)andputc(M, char)to read or write specific bytes.substr(start, end)to extract portions of the text.- The
{}braces can be used seamlessly for string concatenation.
6. Net Types and Implicit Nets
Classic Verilog allowed “implicit nets,” meaning you could use a wire without declaring it. While this saved typing, it is a notorious source of bugs if you accidentally misspell a signal name.
To prevent this, SystemVerilog testbenches and designs should always disable implicit nets using the compilation directive `default_nettype none. Placing this before your modules ensures that any undeclared net will immediately throw a compilation error, saving you hours of debugging.
