Skip to content

VHDL · Chapter 13.3 · Advanced Data Structures

Unconstrained Array Types

An unconstrained array type declares its index range as open and is given a concrete size only where it is used, on a signal, a port, or a subprogram parameter. You have used one since your first lesson, because the standard logic vector type is exactly this kind of type. That openness is a feature you can wield directly, since one type or function can adapt to any width the caller provides, with attributes like range and length reading the actual bounds. It gives width flexibility without a generic, which is why standard functions take unconstrained vectors. This lesson shows how to declare and constrain unconstrained types, how the size attributes work, and how VHDL-2008 extends the idea to unconstrained elements.

Foundation14 min readVHDLArraysUnconstrainedAttributesGenericReuse

1. Engineering intuition — declare the shape, defer the size

Some types should not commit to a size when declared — they should adapt to wherever they are used. std_logic_vector is the obvious case: the type says "a vector of std_logic," and the width is decided at each port, signal, or argument. That is an unconstrained type: the element kind and dimensionality are fixed, but the bounds are left open until use. The payoff is reuse without ceremony — a function that takes an unconstrained vector works on 8, 16, or 32 bits with no generic, because it reads the actual size from the argument via attributes. You declare the shape once; every use pins the size.

2. Formal explanation — open range, constrained at use

unconstrained_types.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- The standard library's std_logic_vector IS unconstrained:
--   type std_logic_vector is array (natural range <>) of std_logic;   -- '<>' = open range
 
-- Your own unconstrained array type:
subtype word_t is std_logic_vector(7 downto 0);
type word_array is array (natural range <>) of word_t;   -- open NUMBER of words
 
-- CONSTRAINED at the point of use (signal/port/parameter gives the bounds):
signal buf  : word_array(0 to 15);                       -- 16 words here
signal data : std_logic_vector(31 downto 0);             -- 32 bits here
 
-- A subprogram parameter stays unconstrained → adapts to the argument via attributes:
function parity (v : std_logic_vector) return std_logic is   -- any width
  variable p : std_logic := '0';
begin
  for i in v'range loop p := p xor v(i); end loop;            -- 'range = the actual bounds
  return p;
end function;

An unconstrained array type uses natural range <> for the open dimension; it cannot be elaborated until a use site (signal, port, subprogram parameter, or aggregate) supplies the bounds. Inside, 'range, 'length, 'high, 'low read the actual constraint, so the code adapts. (VHDL-2008 also permits unconstrained element types — an array of unconstrained vectors.)

3. Production usage — width-flexible ports and functions without a generic

adapts_to_size.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- A port left unconstrained adapts to whatever it is connected to:
entity reducer is
  port ( v : in  std_logic_vector;             -- unconstrained: width set by the connection
         y : out std_logic );
end entity;
architecture rtl of reducer is
begin
  process (v)
    variable acc : std_logic := '0';
  begin
    for i in v'range loop acc := acc or v(i); end loop;   -- works at any connected width
    y <= acc;
  end process;
end architecture;
 
-- Connecting an 8-bit and a 16-bit signal constrains each instance differently — no generic needed.
--   u8  : entity work.reducer port map (v => sig8,  y => r8);    -- v is 8 bits here
--   u16 : entity work.reducer port map (v => sig16, y => r16);   -- v is 16 bits here

What hardware does this become? Whatever the use site fixes: u8 is an 8-input OR reduction, u16 a 16-input one — the unconstrained port is constrained by the signal connected to it, and 'range makes the loop match. There is no runtime "width"; the size is resolved at elaboration just like a generic, but driven by the connection rather than a generic value. This is exactly how numeric_std/std_logic_1164 functions accept any width — they take unconstrained vectors and read 'length.

4. Structural interpretation — one type, sized at each use

one unconstrained type constrained to different widths at different use sitesconstrainconstrainunconstrained typearray (natural range <>)use @ 8 bits'range = 7 downto 0use @ 32 bits'range = 31 downto 0'range / 'lengthlogic adapts to actual size12
An unconstrained array type fixes the element kind and dimensionality but leaves the range open (natural range <>), and each use site supplies the bounds. The same type, port, or function is constrained to 8 bits at one connection and 32 at another; inside, 'range and 'length read the actual size so the logic adapts. std_logic_vector is exactly this, which is why standard functions accept any width without a generic. The size is resolved at elaboration by the use site, not at runtime. This is a type-flexibility structure, captured by a diagram rather than a waveform.

5. Why this is structural, not timing

Unconstrained types are about where size is decided — open at declaration, fixed at use, resolved at elaboration — which is a structural/type property, so the diagram above is the right picture, not a waveform. The resulting hardware behaves like the concrete sized logic each use produces (an 8- or 32-bit reduction), with timing already covered in the combinational material. The lesson's value is flexibility without a generic: by deferring the bound to the use site and reading it with attributes, one definition serves every width — a static, structural form of reuse.

6. Debugging example — using a size that is not there yet

Expected: a function/port adapts to the caller's width. Observed: a compile error that an object is unconstrained / has no bounds, or a 'length used where the size is not yet known, or a hard-coded loop bound that ignores the actual width. Root cause: an unconstrained type was used somewhere a constraint is required (e.g. an unconstrained signal/variable with no initial value or aggregate to fix it), or the body assumed a fixed width instead of reading 'range/'length. Fix: constrain unconstrained objects at their use site (port connection, signal subtype, or an aggregate/initial value), and inside subprograms iterate with 'range and size locals from 'length so they track the actual argument. Engineering takeaway: unconstrained types must be constrained at the point of use, and code inside must read the size with attributes — never assume a fixed width for an unconstrained parameter.

read_the_actual_size.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: assuming a fixed width for an unconstrained parameter.
-- function f(v : std_logic_vector) return std_logic is begin
--   for i in 0 to 7 loop ... end loop;   -- wrong if v is not 8 bits
-- FIX: read the actual bounds with 'range.
for i in v'range loop ... end loop;        -- adapts to v's real width

7. Common mistakes & what to watch for

  • Assuming a fixed width inside an unconstrained subprogram. Use 'range/'length; a literal bound breaks at other widths.
  • Leaving an object unconstrained where a size is required. Signals/variables need a constraint (subtype, initial value, or aggregate); only ports/parameters defer to the use site.
  • Mismatched directions. 'range carries direction (to vs downto); rely on it rather than hard-coding, or normalize when needed.
  • Expecting unconstrained elements pre-2008. Arrays of unconstrained elements need VHDL-2008; older revisions require constrained element types.
  • Confusing unconstrained with generic. Both defer size, but unconstrained takes it from the connection/ argument, a generic from an explicit value — choose per how the size should arrive.

8. Engineering insight & continuity

Unconstrained array types declare shape but defer size to the use sitestd_logic_vector being the everyday example — so one port or function adapts to any width, reading the actual bounds with 'range/'length and giving reuse without a generic. They are the mechanism behind every width-flexible standard function. With arrays, records, multidimensional shapes, and unconstrained sizing covered, the next lesson returns to records as the centerpiece of clean connectivity — Records as Bus Interfaces — bundling a whole bus into one typed interface passed between modules.