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.
Standards & specifications
- Governing standard
- IEEE Std 1076 (VHDL)(opens IEEE in a new tab)
Defines the VHDL language — types, the simulation cycle, and the semantics a conforming analyser and simulator must implement. Synthesis restrictions and vendor coding rules are tool behaviour, not language rules.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of VHDL Data Types — std_logic, Vectors, Arrays & Records.
