VHDL · Chapter 2.7 · Data Types
Enumeration Types
Some signals are not numbers or bit vectors, they are one of a small set of named states such as idle, reading, or writing. An enumeration is the type for exactly that: you list the legal values by name, and VHDL's strong typing guarantees the signal is never anything else. You already use enums, since the logic type, boolean, and character are all predefined enumerations, and your own enums are how every state machine in VHDL names its states. This lesson shows how an enum literal carries no bits until synthesis encodes it into a real representation, why the leftmost value is the silent default, and the two gotchas that bite everyone: uninitialised states and incomplete case statements.
Foundation14 min readVHDLEnumerationFSMState EncodingcaseTypes
1. Intuition — a type that is a list of names
Most of what a design tracks is not a number. A controller is idle or busy; a bus is in its address or data phase; a mode is read or write. An enumeration type captures that directly: you declare the type by listing its legal values as names, and from then on a signal of that type can hold only those names — nothing else compiles.
type state_t is (IDLE, READ, WRITE, DONE); -- four named values, in order
signal state : state_t; -- can ONLY be one of those fourThis is strong typing (lesson 2.1) working for you: there is no way to accidentally assign
5 or 'X' to state, because those are not values of state_t. The type is the list,
and the list is the contract.
2. You already use enumerations
The core VHDL types you have met are themselves enumerations — they are just lists of named values the standard predefines:
-- from std_logic_1164 (lesson 2.2):
type std_ulogic is ('U','X','0','1','Z','W','L','H','-'); -- nine named values
-- from the language itself:
type boolean is (false, true);
type bit is ('0', '1');
-- character is also an enumeration: a list of all the character literalsSo std_logic's nine values, boolean's true/false, and bit's '0'/'1' are not
special machinery — they are ordinary enumerations. Understanding your own enums means you
already understand these.
3. Where enums belong — naming state
The dominant use of a custom enum is naming the states of a finite state machine. Instead
of magic numbers (when "01" =>), you write the states' names and let the tool pick the bits:
library ieee;
use ieee.std_logic_1164.all;
entity ctrl is
port ( clk, rst : in std_logic;
go : in std_logic;
busy : out std_logic );
end entity;
architecture rtl of ctrl is
type state_t is (IDLE, READ, WRITE, DONE);
signal state : state_t; -- defaults to IDLE (leftmost)
begin
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
state <= IDLE;
else
case state is
when IDLE => if go = '1' then state <= READ; end if;
when READ => state <= WRITE;
when WRITE => state <= DONE;
when DONE => state <= IDLE;
end case;
end if;
end if;
end process;
busy <= '0' when state = IDLE else '1';
end architecture;The code reads like the design intent — IDLE, READ, WRITE, DONE — and never mentions
a single bit pattern. That readability is the whole point.
4. Hardware — names you write, bits the tool chooses
An enum literal has no inherent width. IDLE is not "00"; it is just a name. At synthesis
the tool encodes the type — it assigns each literal a bit pattern and builds the state
register from those bits. You influence the scheme; the tool produces the bits.
Because the names are decoupled from the bits, you can change the encoding (binary → one-hot
for speed, gray for low-noise counters) without touching a line of your RTL — the case
still says when READ =>.
5. Enums in simulation — values appear by name
A practical payoff: in a waveform viewer an enum signal shows its literal name, not a bit
pattern. You read IDLE → READ → WRITE → DONE directly, which is why FSMs are so much easier
to debug than hand-encoded std_logic_vector states.
state_t advancing through an FSM — the enum shows by name, not bits
10 cycles6. Debugging example — the leftmost-value trap
The most common enum surprise is a state that is "stuck at the first value." An enum signal with no reset and no initial assignment powers up at its leftmost literal — and in simulation an unresolved path can even leave it there:
type state_t is (IDLE, READ, WRITE, DONE);
signal state : state_t; -- no initialiser → starts at IDLE
-- If your reset never asserts (or you forgot the reset branch), `state`
-- simply sits at IDLE forever and the machine "never starts".
-- There is no error — IDLE is a perfectly legal value — so the bug is silent.The fix is to always give an FSM an explicit reset to a known state, and to make the intended start state the leftmost literal so even a missing reset fails safe:
case state is
when IDLE => ...
-- ...
end case;
-- elsewhere, in the clocked process:
if rst = '1' then state <= IDLE; end if; -- explicit, known startThe second classic is an incomplete case. If you omit a state and have no when others,
some tools error and others infer an unwanted latch. List every literal, or close with
when others => state <= IDLE;.
7. Common mistakes & what to watch for
- No reset / no initial value. The signal sits at the leftmost literal silently. Always reset an FSM to a known state.
- Incomplete
case. Cover every enum literal, or addwhen others =>. Missing branches cause latches or tool errors. - Depending on the encoding. Never assume
IDLE = "00". If you truly need specific bits, use a synthesis attribute (e.g.attribute enum_encoding) — do not hard-code patterns. - Comparing an enum to an integer or
std_logic_vector.state = 1does not compile;stateis astate_t, not a number. Compare to literals (state = READ). - Reusing a literal name across two enum types in the same scope. Overlapping literals
force you to qualify (
state_t'(IDLE)); keep literal names distinct.
8. Engineering insight
An enumeration separates meaning from representation. You program in meaning — IDLE,
READ, DONE — and let synthesis choose the representation — the actual flip-flops and bit
patterns. That separation is what makes VHDL state machines readable, retargetable (flip the
encoding for timing without touching logic), and self-documenting in a waveform. The same
idea underlies the types you already trust: std_logic's nine values and boolean's
true/false are enums whose order was chosen so the defaults behave well. When you design
an enum, choosing the literals — and their order, since the leftmost is the default — is a
small but real piece of hardware design.
9. Summary & next step
An enumeration type is a list of named values; strong typing then allows nothing else.
std_logic, boolean, bit, and character are all predefined enums, and your own enums
name FSM states and modes. Literals carry no bits until synthesis encodes the type (binary,
one-hot, gray), the leftmost literal is the default and 'left, and enums display by name in
simulation. Guard against the silent-default and incomplete-case traps with an explicit
reset and full coverage.
You can now describe values that are names. The chapter turns next to values that are
numbers with width: numeric_std's unsigned and signed — the types that let a bus
actually do arithmetic — and then the conversions that move cleanly between numbers, vectors,
and integers.