An enumerated type defines a set of named values. In the following example, light_* is an enumerated variable that can store one of three possible values (0, 1, 2). By default, the first name in the list gets the value 0, and the following names get incremental values:
enum {RED, YELLOW, GREEN} light_1; // int type; RED = 0, YELLOW = 1, GREEN = 2
enum bit[1:0] {RED, YELLOW, GREEN} light_2; // bit type; RED = 0, YELLOW = 1, GREEN = 2
16 min read | Intermediate Level
What You'll Learn
- How enum values are assigned by default, and what happens when explicit and implicit assignments are mixed
- Why an enum's underlying type must be wide enough to hold every distinct value it needs
- The full range-declaration syntax (
name[N],name[N]=C,name[N:M],name[N:M]=C) and how each one differs - The built-in iteration methods (
first/last/next/prev/num/name), and whynext()/prev()move in declaration order - Why enums are strongly typed, and what happens when that's violated
Basic Enum Declaration
Any integer value can be assigned to any enumerated name. If a name has no assigned value, it automatically takes the incremented value of the previous name:
enum {RED=3, YELLOW, GREEN} light_3; // RED = 3, YELLOW = 4, GREEN = 5
enum {RED = 4, YELLOW = 9, GREEN} light_4; // RED = 4, YELLOW = 9, GREEN = 10 (automatically assigned)
enum {RED = 2, YELLOW, GREEN = 3} light_5; // Error : YELLOW and GREEN are both assigned 3
enum bit[0:0] {RED, YELLOW, GREEN} light_6; // Error: minimum 2 bits are required
light_5 fails because YELLOW auto-increments to 3 from RED's explicit value of 2, colliding with GREEN's own explicit value of 3 — every value within one enum must be unique. light_6 fails for a different reason: bit[0:0] can only hold 2 distinct values (0 and 1), but three enum members need three.
An enumeration name also can't start with a number:
enum {1WAY, 2TIMES, SIXPACK=6} e_formula; // Compilation error on 1WAY, 2TIMES
enum {ONEWAY, TIMES2, SIXPACK=6} e_formula; // Correct way is to keep the first character non-numeric
Quick Check
Q: Would enum {A=5, B, C=5} compile?
Show Answer
No. B auto-increments to 6 from A's explicit value of 5, but C is explicitly set to 5 — directly duplicating A's value. This is the same class of error as light_5 above.
Defining a Custom Enumerated Type With typedef
A custom data type can be created so the same type can be reused to declare other variables:
module tb;
// "e_true_false" is a new data-type with two valid values: TRUE and FALSE
typedef enum {TRUE, FALSE} e_true_false;
initial begin
// Declare a variable of type "e_true_false" that can store TRUE or FALSE
e_true_false answer;
// Assign TRUE/FALSE to the enumerated variable
answer = TRUE;
// Display string value of the variable
$display ("answer = %s", answer.name);
end
endmodule
Output
ncsim> run answer = TRUE ncsim: *W,RNQUIE: Simulation is complete.
Why Do We Need Enumeration?
To make code simpler and more readable. Consider the following without enumeration:
bit [1:0] light;
light = 2'b00; // Assume 00 stands for RED
// After many lines of code we have to correlate what
// 00 stands for - is it RED, YELLOW or GREEN ?
if (light == 2'b00)
// Do something
The following is more readable, thanks to the named values RED, YELLOW, and GREEN:
typedef enum {RED, YELLOW, GREEN} e_light;
e_light light;
light = RED; // Initialize light to RED
// Even after many lines of code it's easier to understand
if (light == RED)
// Do something
Enumerated-Type Ranges
| Form | Effect |
|---|---|
name | The next number will be associated with name |
name = C | Associates the constant C to name |
name[N] | Generates N named constants: name0, name1, ..., nameN-1 |
name[N] = C | The first named constant gets value C, and subsequent ones get consecutive values |
name[N:M] | The first named constant will be nameN and the last nameM, where N and M are integers |
name[N:M] = C | The first named constant, nameN, gets C, and subsequent ones get consecutive values up to nameM |
Example
module tb;
// name : The next number will be associated with name starting from 0
// GREEN = 0, YELLOW = 1, RED = 2, BLUE = 3
typedef enum {GREEN, YELLOW, RED, BLUE} color_set_1;
// name = C : Associates the constant C to name
// MAGENTA = 2, VIOLET = 7, PURPLE = 8, PINK = 9
typedef enum {MAGENTA=2, VIOLET=7, PURPLE, PINK} color_set_2;
// name[N] : Generates N named constants : name0, name1, ..., nameN-1
// BLACK0 = 0, BLACK1 = 1, BLACK2 = 2, BLACK3 = 3
typedef enum {BLACK[4]} color_set_3;
// name[N] = C : First named constant gets value C and subsequent ones
// are associated to consecutive values
// RED0 = 5, RED1 = 6, RED2 = 7
typedef enum {RED[3] = 5} color_set_4;
// name[N:M] : First named constant will be nameN and last named
// constant nameM, where N and M are integers
// YELLOW3 = 0, YELLOW4 = 1, YELLOW5 = 2
typedef enum {YELLOW[3:5]} color_set_5;
// name[N:M] = C : First named constant, nameN will get C and
// subsequent ones are associated to consecutive values until nameM
// WHITE3 = 4, WHITE4 = 5, WHITE5 = 6
typedef enum {WHITE[3:5] = 4} color_set_6;
initial begin
// Create new variables for each enumeration style
color_set_1 color1;
color_set_2 color2;
color_set_3 color3;
color_set_4 color4;
color_set_5 color5;
color_set_6 color6;
color1 = YELLOW; $display ("color1 = %0d, name = %s", color1, color1.name());
color2 = PURPLE; $display ("color2 = %0d, name = %s", color2, color2.name());
color3 = BLACK3; $display ("color3 = %0d, name = %s", color3, color3.name());
color4 = RED1; $display ("color4 = %0d, name = %s", color4, color4.name());
color5 = YELLOW3;$display ("color5 = %0d, name = %s", color5, color5.name());
color6 = WHITE4; $display ("color6 = %0d, name = %s", color6, color6.name());
end
endmodule
Output
ncsim> run color1 = 1, name = YELLOW color2 = 8, name = PURPLE color3 = 3, name = BLACK3 color4 = 6, name = RED1 color5 = 0, name = YELLOW3 color6 = 5, name = WHITE4 ncsim: *W,RNQUIE: Simulation is complete.
In name[N:M], N and M only control the suffix numbers attached to each generated name — they say nothing about the actual enum value each one holds. YELLOW[3:5] generates YELLOW3, YELLOW4, YELLOW5, but their values still start counting from 0 (since nothing precedes them in that specific enum), landing on 0, 1, 2 — not 3, 4, 5. Only adding an explicit = C, as color_set_6 does, controls the starting value directly.
Quick Check
Q: Given typedef enum {CYAN[2:4]=10} c_set;, what value would CYAN3 hold?
Show Answer
11. CYAN2 (the first one) gets the explicit value 10, and each subsequent name increments by one — CYAN3 is 11, CYAN4 is 12.
Enumerated-Type Methods
SystemVerilog includes a set of specialized methods for iterating over the values of enumerated types.
| Method | Signature | Description |
|---|---|---|
first() | function enum first(); | Returns the value of the first member of the enumeration |
last() | function enum last(); | Returns the value of the last member of the enumeration |
next() | function enum next (int unsigned N = 1); | Returns the Nth next enumeration value starting from the current value |
prev() | function enum prev (int unsigned N = 1); | Returns the Nth previous enumeration value starting from the current value |
num() | function int num(); | Returns the number of elements in the enumeration |
name() | function string name(); | Returns the string representation of the given enumeration value |
Enumeration Methods Example
// GREEN = 0, YELLOW = 1, RED = 2, BLUE = 3
typedef enum {GREEN, YELLOW, RED, BLUE} colors;
module tb;
initial begin
colors color;
// Assign current value of color to YELLOW
color = YELLOW;
$display ("color.first() = %0d", color.first()); // First value is GREEN = 0
$display ("color.last() = %0d", color.last()); // Last value is BLUE = 3
$display ("color.next() = %0d", color.next()); // Next value is RED = 2
$display ("color.prev() = %0d", color.prev()); // Previous value is GREEN = 0
$display ("color.num() = %0d", color.num()); // Total number of enum = 4
$display ("color.name() = %s" , color.name()); // Name of the current enum
end
endmodule
Output
ncsim> run color.first() = 0 color.last() = 3 color.next() = 2 color.prev() = 0 color.num() = 4 color.name() = YELLOW ncsim: *W,RNQUIE: Simulation is complete.
next() and prev() move based on declaration order, not numeric value — here they happen to coincide, since colors was declared with plain sequential values. For an enum with custom, non-sequential values, they'd diverge.
Quick Check
Q: If colors were instead declared as typedef enum {GREEN=5, YELLOW=1, RED=9, BLUE=2} colors;, and color = YELLOW, what would color.next() return?
Show Answer
RED, with value 9 — because RED is declared immediately after YELLOW in the source, regardless of the fact that 9 isn't simply "YELLOW's value (1) plus one." next() always follows declaration order, not numeric adjacency.
Type Checking
Enumerated types are strongly typed, so a variable of type enum can't be assigned an integer value outside the enumeration set unless an explicit cast is used:
typedef enum bit [1:0] {RED, YELLOW, GREEN} e_light;
module tb;
e_light light;
initial begin
light = GREEN;
$display ("light = %s", light.name());
// Invalid because of strict typing rules
light = 0;
$display ("light = %s", light.name());
// OK when explicitly cast
light = e_light'(1);
$display ("light = %s", light.name());
// OK. light is auto-cast to integer
if (light == RED | light == 2)
$display ("light is now %s", light.name());
end
endmodule
Behavior on the invalid assignment (light = 0;) varies by simulator. Aldec Riviera Pro treats it as a hard compile error:
ERROR VCP2694 "Assignment to enum variable from expression of different type." "testbench.sv" 11 1 FAILURE "Compile failure 1 Errors 0 Warnings Analysis time: 0[s]."
Cadence's ncsim only warns, and lets simulation proceed:
light = 0; | ncvlog: *W,ENUMERR (testbench.sv,11|11): This assignment is a violation of SystemVerilog strong typing rules for enumeration datatypes. Loading snapshot worklib.tb:sv .................... Done ncsim> run light = GREEN light = RED light = YELLOW ncsim: *W,RNQUIE: Simulation is complete.
Only three lines of output appear, not four — the final if statement's own $display never fires. By that point, light holds YELLOW (value 1, from the explicit cast just before it), which matches neither RED (0) nor the literal 2, so the condition is false and nothing prints for it. The comment "light is auto-cast to integer" describes why the comparison itself is legal syntax — not a claim that this particular condition happens to be true.
Where Is This Used?
Enums are the standard way to represent FSM states, opcodes, and protocol command types in both design and testbench code — named symbolic values are far more readable in waveforms and logs than raw numeric encodings, and strict typing catches an entire class of "accidentally assigned an out-of-range value" bugs at compile time that a plain bit-vector would never catch.
Common Beginner Mistakes
Mistake #1: Assuming an enum's underlying type auto-widens to fit its members
As light_6 shows, an explicitly narrow base type (like bit[0:0]) that can't hold every distinct value needed is a compile error, not something the compiler silently widens for you. Why? Always make sure an explicitly-chosen base type is wide enough for however many distinct values the enum actually needs.
Mistake #2: Mixing implicit and explicit values without checking for collisions
As light_5 and the Quick Check above show, an auto-incremented value can silently collide with a later explicit one. Why? Whenever both styles appear in the same enum, it's worth double-checking that no auto-incremented name ends up duplicating an explicitly-assigned one.
Mistake #3: Assuming next()/prev() follow numeric order
As the second Quick Check shows, they follow declaration order instead — which only coincides with numeric order when the enum's values happen to be sequential. Why? For an enum with custom, non-sequential values, assuming numeric adjacency will give the wrong answer.
Mistake #4: Assigning a plain integer literal to an enum variable without a cast
This violates strict typing rules, and different simulators handle it differently — a hard compile error on some, a warning-but-proceeds on others. Why? Relying on this behavior means the exact same code could fail to compile on one tool while running fine on another — always use an explicit cast (e_light'(value)) when a raw integer genuinely needs to become an enum value.
Practice Exercise
Challenge: Predict a Range Declaration
Given typedef enum {CYAN[4:6] = 20} c_set;, what values do CYAN4, CYAN5, and CYAN6 each hold?
Hint
The first name in the range gets the explicit value directly; each subsequent one increments by one from there — the same pattern as color_set_6 in this article's own example.
Solution
CYAN4 = 20, CYAN5 = 21, CYAN6 = 22. The suffix numbers (4, 5, 6) only determine the generated names — the explicit = 20 sets where the actual values start counting from, exactly as WHITE[3:5] = 4 did earlier in this article.
Summary
In this tutorial, you learned:
- Enum values default to incrementing from 0, but any name can be given an explicit value, and unassigned names auto-increment from whatever came before
- An enum's underlying type must be wide enough to hold every distinct value it needs — the compiler won't widen it automatically
name[N:M]'s suffix numbers control only the generated names, not the actual values, which still start from 0 unless an explicit= Cis also givennext()andprev()move based on declaration order, not numeric value order- Enums are strongly typed — assigning a raw integer without an explicit cast violates that typing, with behavior that varies by simulator