SystemVerilog Custom Types and Enumeration Fundamentals
SystemVerilog significantly extends Verilog by allowing engineers to construct custom net and variable types. These user-defined types enable modeling complex hardware at a higher abstraction level while remaining synthesizable. By leveraging these types, designers can write less code that is more self-documenting and easier to maintain. This guide covers:
- Creating custom types using
typedef - Defining enumerated types with
enum - Manipulating and iterating through enumerated values
4.1 Custom Type Definitions
Unlike standard Verilog, which lacks extensibility for net and variable types, SystemVerilog introduces mechanisms to define new types suitable for system-level and architectural modeling. This is achieved using the typedef keyword, similar to C.
4.1.1 Local vs. Shared Definitions
Custom types can be scoped locally within a module or interface, or shared globally via packages.
Local Scope: When a type is specific to a block, define it inside that module. In the example below, nibble is local to the alu_core module.
module alu_core (...);
typedef logic [3:0] nibble;
nibble operand_a, operand_b;
nibble [7:0] data_bus;
...
endmodule
Shared Scope (Packgaes): For types used across the design, place them in a package. You can reference them diretcly or import them.
Example 4-1: Direct Reference
package system_pkg;
`ifdef TWO_STATE
typedef bit data_word_t;
`else
typedef logic data_word_t;
`endif
endpackage
module counter
(output system_pkg::data_word_t [15:0] count,
input system_pkg::data_word_t clock, resetN);
always @(posedge clock, negedge resetN)
if (!resetN) count <= 0;
else count <= count + 1;
endmodule
Importing to Compilation Unit: To avoid prefixing the package name on every port, import the definition into $unit.
Example 4-2: Importing Definitions
package system_pkg;
`ifdef TWO_STATE
typedef bit data_word_t;
`else
typedef logic data_word_t;
`endif
endpackage
import system_pkg::data_word_t;
module counter
(output data_word_t [15:0] count,
input data_word_t clock, resetN);
always_ff @(posedge clock, negedge resetN)
if (!resetN) count <= 0;
else count <= count + 1;
endmodule
If a package contains many types, a wildcard import (import system_pkg::*;) can be used.
4.1.2 Naming Conventions
To distinguish custom types from variables, it is standard practice to suffix type names with _t (e.g., data_word_t). This enhances readability in large codebases.
4.2 Enumerated Types
Enumerated types (enum) allow variables to hold a specific set of named values (labels). This makes the code more abstract and readable compared to using raw constants or defines.
4.2.1 The Problem with Verilog Constants
In Verilog, pseudo-enums are created using parameter or `define. However, the simulator or synthesizer does not restrict the variable to those specific values. A state variable declared as a reg can technically hold any value (like X or Z), leading to potential mismatches between simulation and synthesis.
4.2.2 SystemVerilog Enumeration
SystemVerilog enum restricts variables to the defined label set. Tools treat these limits consistently.
Example 4-4: State Machine with Enums
package system_pkg;
typedef enum {FETCH, WRITE, ADD, SUB, MULT, DIV, SHIFT, NOP } instruction_t;
endpackage
import system_pkg::*;
module controller (output logic read, write,
input instruction_t cmd,
input wire clock, resetN);
enum {IDLE, DECODE, EXECUTE} curr_state, next_state;
always_ff @(posedge clock, negedge resetN)
if (!resetN) curr_state <= IDLE;
else curr_state <= next_state;
always_comb begin
case (curr_state)
IDLE: next_state = DECODE;
DECODE: next_state = EXECUTE;
EXECUTE: next_state = IDLE;
endcase
end
always_comb begin
read = 0; write = 0;
if (curr_state == DECODE && cmd == FETCH) read = 1;
else if (curr_state == EXECUTE && cmd == WRITE) write = 1;
end
endmodule
Note on Imports: Importing an enum type (e.g., import system_pkg::instruction_t;) does not automatically make the labels (FETCH, WRITE) visible. You must use a wildcard import (import system_pkg::*;) or import labels explicitly to use them directly.
4.2.3 Labels, Values, and Base Types
- Default Values: Labels default to
intvalues starting at 0, 1, 2, etc. - Custom Values: You can assign specific values (e.g.,
enum {ONE=1, TWO, THREE} val;). - Label Sequences: Shorthand ranges are allowed:
enum {START, S[5], E[6:9]} state; - Base Type: By default, the base type is
int(32-bit, 2-state). You can specify a hardware-specific base type, such aslogic [1:0]for finite state machine encoding.
// 4-state base type enum
enum logic [1:0] {WAITE = 2'b01, LOAD = 2'b10, READY = 2'b11} state;
// 2-state base type enum
enum bit {TRUE, FALSE} status_flag;
Values must match the size of the base type. Assigning a 3-bit value to a default int enum is illegal.
4.2.4 Strong Typing and Casting
Enumerated types are strongly typed. You cannot assign a standard integer directly to an enum variable.
typedef enum {A, B, C} my_state_t;
my_state_t state1, state2;
int x;
state1 = state2; // Legal: same type
x = state1 + 1; // Legal: operation uses base type (int)
state1 = x + 1; // ILLEGAL: int cannot be assigned to enum
Casting: To assign operation results, use casting.
- Static Cast:
state1 = my_state_t'(state2 + 1);(Fast, no runtime check, may assign out-of-range values). - Dynamic Cast:
$cast(state1, state2 + 1);(Safe, checks if value is valid at runtime, reports error if out of range).
4.2.5 Enumeration Methods
SystemVerilog provides built-in methods to navigate enum lists using C++ style syntax (variable.method()).
state.first(): Returns the first label.state.last(): Returns the last label.state.next(N): Returns the Nth next label (wraps around).state.prev(N): Returns the Nth previous label (wraps around).state.num(): Returns the number of labels.state.name(): Returns the string name of the current value.
Example 4-5: Using Iteration Methods
module confidence_counter(input logic synced, compare, resetN, clock,
output logic in_sync);
enum {cnt[0:15]} State, Next;
always_ff @(posedge clock, negedge resetN)
if (!resetN) State <= cnt0;
else State <= Next;
always_comb begin
Next = State;
case (State)
cnt0 : if (compare && synced) Next = State.next;
cnt1 : begin
if (compare && synced) Next = State.next;
if (compare && !synced) Next = State.first;
end
cnt15: if (compare && !synced) Next = State.prev(2);
default begin
if (compare && synced) Next = State.next;
if (compare && !synced) Next = State.prev(2);
end
endcase
end
always_ff @(posedge clock, negedge resetN)
if (!resetN) in_sync <= 0;
else begin
if (State == cnt8) in_sync <= 1;
if (State == cnt0) in_sync <= 0;
end
endmodule
4.2.6 Displaying Enums
Use the .name() method to print the label string instead of the numerical value.
enum logic [2:0] {WAITE=3'b001, LOAD=3'b010, READY=3'b100} State, Next;
always_comb begin
$display("Current: %s (%b)", State.name, State);
case (State)
WAITE: Next = LOAD;
LOAD: Next = READY;
READY: Next = WAITE;
endcase
end