VHDL Array

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;
Array Indexing Explorer
Click on an element to see how to access it in VHDL
type t_vector is array (0 to 7) of integer;
VHDL Access
Click an element above

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

[3] Altera/Intel Cyclone II

[4] Altera Quartus II