Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

SystemVerilog Custom Types and Enumeration Fundamentals

Notes May 8 3

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 int values 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 as logic [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

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.