What is an array
In any software programming language, when we need to deal with a collection of elements of the same type we can take advantage of the dedicated data structures provided by the language. In VHDL this kind of structure is defined “array”.
We can collect any data type object in an array type. Many of the predefined VHDL data types — such as string, bit_vector, and std_logic_vector — are themselves defined as an array of a basic data type. The range <> syntax indicates that the array bounds are left unspecified (unconstrained) and will be defined later when the object is declared.
An example is:
type string is array (positive range <>) of character;
type bit_vector is array (natural range <>) of bit;

Figure 1 – example of VHDL array definition and addressing
VHDL array declaration
VHDL arrays can be either one-dimensional (with one index) or multidimensional (with two or more indices).
When we declare an array, we can decide if the array is
- Constrained
- Unconstrained
In the constrained array, the bounds for an index are established when the array type is defined.
In the unconstrained array, the bounds are established subsequently during the declaration of the variable or signal.
The BNF for declaring an array type is:
array_type_definition ::= unconstrained_array_definition | constrained_array_definition
unconstrained_array_definition ::=
array ( index_subtype_definition { , index_subtype_definition } ) of element_subtype_indication
constrained_array_definition ::=
array index_constraint of element_subtype_indication
index_subtype_definition ::= type_mark range <>
index_constraint ::= ( discrete_range { , discrete_range } )
discrete_range ::= discrete_subtype_indication | range
subtype_declaration ::= subtype identifier is subtype_indication ;
subtype_indication ::= [ resolution_function_name ] type_mark [ constraint ]
type_mark ::= type_name | subtype_name
constraint ::= range_constraint | index_constraint
The subtype allows the values taken on by an object to be restricted or a constrained subset of some base type.
Some examples of constrained array type declarations:
type t_vector is array (integer range <>) of real;
type t_word is array (31 downto 0) of bit;
type t_memory is array (address) of word;
type t_transform is array (1 to 4, 1 to 4) of real;
type string is array (positive range <>) of character;
type bit_vector is array (natural range <>) of bit;
signal mystring : string(1 to 255);
variable mybitvector : bit_vector(15 downto 0);
Accessing the array object
An element of an array object can be referred to by indexing the name of the object. The index must be within the declared range of the array; accessing an element outside the range will cause an error at simulation time or unpredictable behavior in synthesis.
Figure 1 reports an example of the signal vector and matrix addressing, below is the VHDL code for matrix and vector definition and addressing.
type t_matrix is array (0 to 3, 0 to 7) of integer; -- matrix 4 x 8
type t_vector is array (0 to 3) of integer; -- vector 4 element
signal matrix : t_matrix;
signal vector : t_vector;
matrix(0,2) <= 5;
vector(1) <= 42;
Implementing a MUX using an array in VHDL
In this post, we describe the VHDL implementation of a MUX using the CASE-WHEN statement. Another compact and elegant way for describing a MUX architecture in VHDL is to use the array approach. The VHDL code below reports the implementation of an 8-way MUX. We define an array of the same type of data of the MUX, then the array is referenced using the MUX selector. In a single VHDL line, we can implement an N-way MUX.
library ieee ;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity array_mux8 is
port(
d0 : in std_logic_vector(1 downto 0);
d1 : in std_logic_vector(1 downto 0);
d2 : in std_logic_vector(1 downto 0);
d3 : in std_logic_vector(1 downto 0);
d4 : in std_logic_vector(1 downto 0);
d5 : in std_logic_vector(1 downto 0);
d6 : in std_logic_vector(1 downto 0);
d7 : in std_logic_vector(1 downto 0);
s : in std_logic_vector(2 downto 0);
m : out std_logic_vector(1 downto 0));
end array_mux8;
architecture rtl of array_mux8 is
type t_array_mux is array (0 to 7) of std_logic_vector(1 downto 0);
signal array_mux : t_array_mux;
begin
array_mux(0) <= d0;
array_mux(1) <= d1;
array_mux(2) <= d2;
array_mux(3) <= d3;
array_mux(4) <= d4;
array_mux(5) <= d5;
array_mux(6) <= d6;
array_mux(7) <= d7;
m <= array_mux(to_integer(unsigned(s)));
end rtl;
For example, Figure 2 shows the RTL view of the 8-way MUX implementation on Altera/Intel Cyclone II FPGA

Figure 2 – 8-way MUX RTL view
Implementing a LUT using an array in VHDL
A typical application of array in VHDL is the implementation of a LUT aka Look Up Table. In the example below is reported a vector of integer whose range is 0 to 15 i.e. 4 bit unsigned. The LUT can be initialized in different ways as in the VHDL example below:
library ieee ;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity array_lut8 is
port(
s1 : in std_logic_vector(2 downto 0);
s2 : in std_logic_vector(2 downto 0);
m1 : out std_logic_vector(3 downto 0);
m2 : out std_logic_vector(3 downto 0));
end array_lut8;
architecture rtl of array_lut8 is
type t_array_lut is array (0 to 7) of integer range 0 to 15;
constant C_LUT1 : t_array_lut := (
8 ,
14 ,
13 ,
9 ,
1 ,
3 ,
5 ,
11);
constant C_LUT2 : t_array_lut := (
5 => 13 ,
4 => 9 ,
3 => 1 ,
7 => 8 ,
6 => 14 ,
2 => 3 ,
1 => 5 ,
0 => 11);
begin
m1 <= std_logic_vector(to_unsigned(C_LUT1(to_integer(unsigned(s1))),4));
m2 <= std_logic_vector(to_unsigned(C_LUT2(to_integer(unsigned(s2))),4));
end rtl;
Figure 3 shows the technology view of the LUT implementation on Altera/Intel Cyclone II FPGA

Figure 3 – LUT implementation technology view
Implementing a Digital Signal Processing section using an array
Another typical example of array usage is when you need to perform the same operation on a large number of signals. An example is adding two sets of input number in the code below:
library ieee ;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity array_add8 is
port(
-- first set op openrand
i_op10 : in std_logic_vector(3 downto 0);
i_op11 : in std_logic_vector(3 downto 0);
i_op12 : in std_logic_vector(3 downto 0);
i_op13 : in std_logic_vector(3 downto 0);
i_op14 : in std_logic_vector(3 downto 0);
i_op15 : in std_logic_vector(3 downto 0);
i_op16 : in std_logic_vector(3 downto 0);
i_op17 : in std_logic_vector(3 downto 0);
-- second set op openrand
i_op20 : in std_logic_vector(3 downto 0);
i_op21 : in std_logic_vector(3 downto 0);
i_op22 : in std_logic_vector(3 downto 0);
i_op23 : in std_logic_vector(3 downto 0);
i_op24 : in std_logic_vector(3 downto 0);
i_op25 : in std_logic_vector(3 downto 0);
i_op26 : in std_logic_vector(3 downto 0);
i_op27 : in std_logic_vector(3 downto 0);
-- output
o_add0 : out std_logic_vector(4 downto 0);
o_add1 : out std_logic_vector(4 downto 0);
o_add2 : out std_logic_vector(4 downto 0);
o_add3 : out std_logic_vector(4 downto 0);
o_add4 : out std_logic_vector(4 downto 0);
o_add5 : out std_logic_vector(4 downto 0);
o_add6 : out std_logic_vector(4 downto 0);
o_add7 : out std_logic_vector(4 downto 0));
end array_add8;
architecture rtl of array_add8 is
type t_array_op is array (0 to 7) of signed(4 downto 0);
signal w_op1 : t_array_op;
signal w_op2 : t_array_op;
signal w_add : t_array_op;
begin
w_op1(0) <= to_signed(to_integer(signed(i_op10)),5);
w_op1(1) <= to_signed(to_integer(signed(i_op11)),5);
w_op1(2) <= to_signed(to_integer(signed(i_op12)),5);
w_op1(3) <= to_signed(to_integer(signed(i_op13)),5);
w_op1(4) <= to_signed(to_integer(signed(i_op14)),5);
w_op1(5) <= to_signed(to_integer(signed(i_op15)),5);
w_op1(6) <= to_signed(to_integer(signed(i_op16)),5);
w_op1(7) <= to_signed(to_integer(signed(i_op17)),5);
w_op2(0) <= to_signed(to_integer(signed(i_op20)),5);
w_op2(1) <= to_signed(to_integer(signed(i_op21)),5);
w_op2(2) <= to_signed(to_integer(signed(i_op22)),5);
w_op2(3) <= to_signed(to_integer(signed(i_op23)),5);
w_op2(4) <= to_signed(to_integer(signed(i_op24)),5);
w_op2(5) <= to_signed(to_integer(signed(i_op25)),5);
w_op2(6) <= to_signed(to_integer(signed(i_op26)),5);
w_op2(7) <= to_signed(to_integer(signed(i_op27)),5);
o_add0 <= std_logic_vector(w_add(0));
o_add1 <= std_logic_vector(w_add(1));
o_add2 <= std_logic_vector(w_add(2));
o_add3 <= std_logic_vector(w_add(3));
o_add4 <= std_logic_vector(w_add(4));
o_add5 <= std_logic_vector(w_add(5));
o_add6 <= std_logic_vector(w_add(6));
o_add7 <= std_logic_vector(w_add(7));
p_add : process(w_op1,w_op2)
begin
for k in 0 to 7 loop
w_add(k) <= w_op1(k) + w_op2(k);
end loop;
end process p_add;
end rtl;
Figure 4 shows the RTL view of the 8-adder implementation on Altera/Intel Cyclone II FPGA using the array approach.

Figure 4 – adder implementation RTL view
Conclusion
As you saw in the previous examples, using arrays allows you to write very compact and elegant VHDL code. Moreover, using the array approach you minimize coding errors since the VHDL code is more compact and simple to read and understand.
The key advantage of arrays is that they make your code scalable: adding more elements often requires only changing the array range, not duplicating logic.
However, remember that arrays are not free. A large array can infer a significant amount of hardware resources. The rule is always the same:
Think hardware!
Every time you write VHDL code, check the area report to see if it matches your design intention and when you are not sure about the VHDL synthesis, check the RTL viewer and technology view.
Reference
[1] RTL HARDWARE DESIGN USING VHDL Coding for Efficiency, Portability, and Scalability
[2] VHDL Programming by Example 4th Ed Douglas – Perry
[4]Â Altera Quartus II