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 such kind of structure is defined “array“.

We can collect any data type object in an array type, many of the predefined VHDL data types are defined as an array of a basic data type.

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

The VHDL Arrays can be may both 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
  • Urnconstrained

In the constrained array, he 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 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 to the array object

An element of an array object can be referred to by indexing the name of the object.

Figure 1 reports an example of the signal vector and matrix addressing, here below 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. In the VHDL code below is reported the VHDL code of the implementation of an 8-way MUX. We defined 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;

As 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 is 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 a signal. 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 example, using array allow you writing a very compact and elegant VHDL code. Moreover, using array approach you minimize the coding error since the VHDL code is more compact and simple to read and understand. After all these advantages, you should pay a great attention to your VHDL. Using the array coding style, you can fill a huge FPGA with only just few line of VHDL code! The rule is always the same:

Think hardware!

Every time you write a VHDL code, check the area report if it matches with your design intention and when you are not sure of 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] The VHDL Cookbook

[4] Altera/Intel Cyclone II

[5] Altera Quartus II

 

 

Leave a Reply

Your email address will not be published.