VHDL · Chapter 2.6 · Data Types
Integer Types and Ranges
Not every value in a design is a bus. Indices, counts, loop bounds, and generics are numbers, and VHDL's integer is the abstract whole-number type for them, with no bit positions and no width, just a value with a range. This lesson shows why an unconstrained integer is 32 bits wide, how adding a range constraint shrinks it to exactly the hardware you want, and how it differs from the unsigned type of the numeric library, which is bits that stand for a number. It also covers the gotcha that catches everyone: exceeding an integer's range is a runtime error, not the silent wrap-around an unsigned value would give you, so a counter needs its range chosen with care.
Foundation14 min readVHDLintegerRangesSubtypesnumeric_stdCounters
1. Intuition — an abstract number, not a bus
std_logic_vector is wires; integer is a number. It has no bit positions you can index,
no 'Z' or 'X', no width you declare — just a whole-number value. You use it wherever the
design needs to count or index rather than route bits: loop variables, array indices,
generics, and the value inside a counter.
Because it is abstract, the tool is free to implement it in as many or as few bits as the value needs — if you tell it the range. That single idea, range determines width, is what this lesson is about.
2. Range determines width
An unconstrained integer spans the full 32-bit signed range (about ±2.1 billion). Declared
as a signal it implies 32 bits of hardware — almost always far more than you need. Adding a
range constraint tells synthesis the real bounds, and it builds only the bits required:
signal idx : integer range 0 to 15; -- 4 bits — a small index
signal level : integer range 0 to 255; -- 8 bits — a byte-sized count
-- signal big : integer; -- 32 bits — avoid in real signals
-- handy predefined subtypes:
signal n : natural; -- integer range 0 to integer'high (non-negative)
signal p : positive; -- integer range 1 to integer'high (strictly positive)natural and positive are just constrained integer subtypes the standard predefines —
use them to document intent (a count cannot be negative; a divisor cannot be zero).
3. Where integers belong — indices, loops, generics
integer is the natural type for anything that counts or selects, where you never need to
touch individual bits:
-- loop variable: an integer, ranged by the loop bounds
gen : for i in 0 to 7 generate
y(i) <= a(i) xor b(i);
end generate;
-- array index: integers select elements
data_out <= mem(addr); -- addr is an integer index into mem
-- generic: a compile-time integer parameter
entity fifo is
generic ( DEPTH : positive := 16 ); -- positive integer, ranged and defaulted
port ( ... );
end entity;In each case the value is a number used to count or pick, not a bus to be wired — which is
exactly what integer is for.
4. The range bound over time — and the wrap gotcha
A constrained integer counter behaves like any counter, but its range bound is a hard limit
you must respect. The waveform shows a range 0 to 5 counter that wraps because the code
explicitly resets it — without that reset, hitting 6 is a runtime error, not a wrap:
integer range 0 to 5 counter — counts, then wraps only because the code resets it
14 cyclessignal count : integer range 0 to 5;
process (clk)
begin
if rising_edge(clk) then
if count = 5 then
count <= 0; -- explicit wrap — REQUIRED to stay in range
else
count <= count + 1;
end if;
end if;
end process;5. Debugging example — "value 6 is out of range 0 to 5"
This is the integer gotcha, and the error message is exact:
signal count : integer range 0 to 5;
-- count <= count + 1; -- if count is already 5, this assigns 6
-- → simulation stops: "value 6 is out of the range 0 to 5 of signal count"Read it literally: you drove a constrained integer past its declared bound, and VHDL treats
that as an error — because a value outside the range is meaningless for that type, not a
modulo wrap. The fix is to guard the increment (the if count = 5 above) or, if you actually
want wrap-around arithmetic, use unsigned from numeric_std, which wraps by design:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
signal c : unsigned(2 downto 0); -- 0..7, wraps 7 → 0 automatically
-- c <= c + 1; -- legal: unsigned arithmetic wraps, no error6. Common mistakes & what to watch for
- Leaving an integer unconstrained. A bare
integersignal is 32 bits — wasteful, and often rejected by synthesis. Always give arange. - Expecting an integer to wrap. It does not — exceeding the range is a runtime error.
Guard it, or use
unsigned/signedfor wrap-around. - Using
integerwhere you need bit access. You cannot index an integer's bits; if you must slice or manipulate bits, it should be a vector (unsigned/signed/std_logic_vector). - Forgetting
natural/positive. They are free, documenting constraints (no negatives; no zero) the compiler then enforces.
7. Engineering insight
integer and unsigned are not redundant — they sit on opposite sides of the
abstract/concrete line. integer is a pure number with a range: ideal for indices, loop
bounds, and generics, where the value matters and the bits do not. unsigned/signed are
bits that mean a number: ideal where width, bit access, or wrap-around matter. Constrain
your integers so they synthesize small and so out-of-range bugs surface immediately, and
reach for numeric_std the moment you need defined-width, wrapping arithmetic.
8. Summary & next step
VHDL's integer is an abstract whole-number type for counts, indices, loop variables, and
generics. Unconstrained it is 32 bits; a range constraint shrinks it to the minimal
synthesized width and is essentially mandatory in real RTL. It is not a bus — no bit
access — and it does not wrap: exceeding the range is a runtime error, so use unsigned/
signed when you need wrap-around. natural and positive are predefined constrained
subtypes worth using for intent.
You now have both worlds of scalar value: logic-valued (std_logic) and number-valued
(integer). The chapter continues by tying them together — numeric_std's unsigned and
signed, the types that let buses do arithmetic — and the conversions that move cleanly
between all of them.