VHDL-4--VHDL operators

Learn VHDL syntax

VHDL operators

Objectives of this section

What is composite data types?

The composite type object has a group of elements.

Composite types are of two types:

The differences between Array and Record are

Their usages

bit_vector, std_logic_vector and string are all pre-defined composite types (they are array type)

Example:

signal A_WORD: std_logic_vector (3 downto 0) := "0011";

The A_WORD signal shown here has a composite data type.

1. std logic vector and signed, unsigned

a. std logic vector

b. signed and unsigned

entity bcd_add_and_display is
    port(
        clk         : in std_logic;
        bcd_a_in    : in unsigned (3 odwnto 0);
        bcd_b_in    : in unsigned (3 downto 0);
        led_7seg_out: out std_logic_vector (6 downto 0)

    );
end entity;

c. What can and cannot be done with std_logic?

You can use std_logic with:

(*) Considering that std_logic has nine different potential values, comparing std_logic will compare its position of an enumerated type against the position of the other item’s enumerated type. Most likely, this expected outcome will not be realized. Therefore, it only makes sense to test for equality and inequality.

The NUMERIC_STD library contains appropriate overloading functions to support various comparisons and shifting functions. It also contains special cases of the std_logic type like signed and unsigned types.

2. Arrays

a. Arrays

Arrays are groups of elements, all of the same type. The syntax of an array declaration is shown here.

type <new type name> is array ( <range> ) of <data_type>

In this:

type WORD is array (0 to 3) of std_logic;
signal B_BUS : WORD;

In this example, WORD is an array of four elements of the type std_logic.

If a signal B_BUS is of type WORD, then it becomes an array of std_logic. Thus, the possible values for each element are ‘U’, ‘X’, ‘0’, ‘1’, ‘Z’, ‘W, ‘U, ‘H’, or ‘-‘. Therefore, there are 9^4 (6561) possibilities versus 16 if the type were “bit”.

type DATA is array (0 to 3) of integer range 0 to 9;
signal B_BUS : DATA;

In this example, DATA is an array of four elements of type integer. Here, the integer range is reduced to 0 to 9. This is important to avoid the default (minimum) 32-bit implementation. If the signal B_BUS is of type DATA, then it becomes an array of integers

b. Array assignments

When you want to assign one array to another?

Make sure that the arrays are of

signal BUS_A, BUS_B :   std_logic_vector (3 downto 0);
signal BUS_C:           std_logic_vector (0 to 3);

Positional assignment means that the arrays are lined up as they were declared either (3 downto 0) or (0 to 3). Positions in the array are then matched and the assignment is made.

Positional assignment

In the first example, BUS_A and BUS B are arrays of std_logic_vector with four elements each. The positional assignment happens as shown as the elements are arranged in a downward direction.

In the second example, the array BUS_A is arranged in a downward direction and the array BUS_C is arranged in an upward direction. Hence, bit swapping happens as shown

c. Array assignment notation

To simplify array assignments and enhance readability:

signal DATA_WORD : std_logic_vector(11 downto 0);

DATA_WORD <= X"A6F";
DATA_WORD <= "101001101111";
DATA_WORD <= O"5157";
DATA_WORD <= B"1010_0110_1111";

Vivado synthesis only allows underscores when the base is explicitly defined via hex, octal, or binary notation.

signal DATA_WORD : std_logic_vector(10 downto 0);
DATA_WORD <= B"100_0110_1111";
DATA_WORD <= X"46F";

Example 2 illustrates that the size of the vector must be an integer multiple of the base. That is, to use the binary base, the length of the vector must be divisible by two. This is not the case here as the length of the DATA WORD is eleven. Similarly, for hexadecimal base, the length of the vector must be divisible by sixteen, but it is twelve in this case. Hence, both the assignments show an error. [Note: I think the first assignment of binary base is correct]

d. Array slices

You can reference any group of contiguous elements within an array as a slice.

A slice is a sub-array of a one-dimensional array, from a single element up to a complete array.

The example has A, B, and Z as arrays, where A and B have eight elements and Z has 16 elements.

signal A_VEC,B_VEC  : std logic vector (7 downto 0);
signal Z_VEC        : std _logic_vector (15 downto 0);
signal A_BIT, C_BIT, D_BIT: std_logic;

Z_VEC(15 downto 8) <= A VEC;
B_VEC <= Z_VEC (12 downto 5);
A_VEC (1 downto 0) <= C_BIT & D_BIT;
Z_VEC (5 downto 1) <= B_VEC(1 to 5);

Out of the four array assignments:

Array slices example

entity REG_4 is
port (
        reset   : in std logic.
        clk     : in std logic;
        d_in    : in std_logic_vector (3 downto 0);
        cntrl   : in std_logic_vector (1 downto 0);
        q       : out std_logic_vector (3 downto 0)
    );
end entity REG_4;
signal A : std_logic;
signal B : bit;
signal C : std_logic;
signal D : integer;
signal E : std_logic_vector (3 downto 0);

A <= C GOOD
A <= cntrl(1) GOOD
E <= d_in+1 Depends
A <= B ERROR
D <= E ERROR
B <= D ERROR
Q <= cntrl ERROR

The VHDL code here has reset and clock inputs with the std_logic type, and the d_in and cntrl inputs as 4-bit and 2-bit arrays, respectively. q is an output array of 4 bits. The code also defines A, B, C, D, and E as signals.

e. String

String is a user-defined array of characters.

constant WARNING1: string (1 to 27) := "Unexpected Outputs Detected";
write(output, WARNING1 & '!');

f. Memory as Pseudo 2D arrays

A convenient way of modeling memory structures is to create a pseudo two-dimensional array structure; that is, create an array of arrays.

type MEM_ARRAY is array (0 to 3) of std_logic_vector (7 downto 0);
signal MY_MEM : MEM_ARRAY;

In the example here, a MEM_ARRAY is an array of four elements and each element itself is an array of the std_logic type.

Once this array is declared, a signal MY_MEM is created of the MEM_ARRAY type.

Synthesis tools understand this construct as a way to represent memory.

Memory modeling

g. Assigning values to arrays

type MEM_ARRAY is array (0 to 3) of std_logic_vector (7 downto 0);
signal MY_MEM : MEM_ARRAY;
signal R_ADDR, W_ADDR : std_logic_vector (1 downto 0);

The MEM_ARRAY here is an array of arrays and MY_MEM is a signal of that type.

R_ADDR and W_ADDR are arrays used for read addresses and write addresses.

In order to assign a value to an array, first the read and write address vector is converted to an integer and ther references a row within an array.

Thus, the reading of data happens through the first line of the code shown here, and the writing of data happens through the second.

D_OUT <= MY_MEM (to_integer(unsigned(R_ADDR)));
...
MY_MEM (to_integer(unsigned(W_ADDR))) <= DATA_IN;

h. Higher order arrays

You can construct true multi-dimensional arrays by creating a user-defined type of the desired size.

The example here creates a two-dimensional array MATRIX A of the std_logic type and a three-dimensional array MATRIX_B of integers.

type MATRIX_A is array (1 to 3, 1 to 16) of std_logic;
type MATRIX_B is array (1 to 3, 1 to 3, 1 to 3) of integer range -1024 to 1023;

type MATRIX_3x16        : MATRIX_A  := (others => (others => '0'));
type MATRIX_3x3x3       : MATRIX_B  := (others => (others => (others => '0')));
MATRIX_3x16 (3,15) <= '1';
value <= MATRIX_3x3x3 (2,1,3);

The example here shows how to multiply a higher order array by an integer scalar.

mmult: process
    type tMATRIX_3x3x3 is array(1 to 3, 1 to 3, 1 to 3) of integer range -1024 to 1023;
    variable matrix3D   : tMATRIX_3x3x3 := (others=>(others=>(others=>0)));
    constant k          : integer := 7;
begin
    -- sparsely populate the matrix
    matrix3D(1,2,3) := 4;   matrix3D(1,3,1) := -10;
    matrix3D(2,2,2) := 100; matrix3D(2,3,4) := -50;

    -- Loop through the matrix and multiply each element by a scalar
    rowLoop: for row in 1 to 3 loop
        colLoop: for col in 1 to 3 Joop
            dthLoop: for depth in 1 to 3 Toop
                report  integer’ image (row) & & integer’ image(col) & "," &
                        integer image(depth) & " * " & integer’ image(k) &
                matrix3D (row,col, depth) := k * matrix3D (row,col,depth);
                report " " & integer'image(matrix3D (row,col,depth));
            end loop dthLoop;
        end loop colLoop;
    end loop rowLoop;
wait;
end process mult;

In this example, a higher order array having three dimensions is shown, where each individual element inside this array is of the integer type.

A variable matrix3D of this type is then created and initialized to zero.

Each element of this variable matrix3D is then multiplied by a scalar constant to get the result.

3. Records

a. What is record?

A record is a group of elements that may be of different types. An example of record is given here.

type PACKET is record
PARITY      : bit
ADDRESS     : std_logic_vector (0 to 3);
DATA_BYTE   : std_logic_vector (7 downto 0);
NUM_VALUE   : integer range (0 to 6);
STOP_BITS   : bit_vector (1 downto 0);
end record;
...
signal TX PACKET, RX PACKET : PACKET;

PACKET is a record with elements like:

These elements are stored one after the other as shown here.

PACKET record

b. Arrays of records

For packet-handling applications, an array of records can be useful. The ability to combine different data types affords great flexibility.

type PACKET is record
PARITY      : bit
ADDRESS     : std_logic_vector (0 to 3);
DATA_BYTE   : std_logic_vector (7 downto 0);
NUM_VALUE   : integer range (0 to 6);
STOP_BITS   : bit_vector (1 downto 0);
end record;
...
signal TX PACKET, RX PACKET : PACKET;

The PACKET here is a record having five elements, out of which PARITY is of the bit type and the rest are arrays of the std_logic_vector type, each having a different array length.

type DATA_ARRAY is array (0 to 2) of PACKET;
signal MY_DATA : DATA_ARRAY;

Once this PACKET record is declared, a new type DATA_ARRAY, an array of record PACKET having three elements, is created.

A new signal MY_DATA is then declared which is of the DATA ARRAY type. The diagram here shows a pictorial representation of MY_DATA, showing all three elements.

4. Array aggreates and record aggregates

a. Array aggregates

Aggregates are a convenient means for grouping both scalar and composite data types for assignment.

They are enclosed in parentheses and separated by commas.

Only scalar data variables are allowed on the left-side aggregates.

signal H_BYTE, L_BYTE   : std_logic_vector (0 to 7);
signal Q_OUT            : std_logic_vector (31 downto 0);
signal A, B, C, D       : std_logic;
signal WORD             : std_logic_vector (3 downto 0);

(A, B, C, D) <= WORD;

An aggregate of A, B,C, and D has been assigned a WORD, which is an array of std_logic. This is valid since both sides have the same length and the same std logic type.

WORD <= (2 => '1', 3 => D, others => '0');

In this example, index 2 and 3 of the WORD array have been assigned specific values of ‘1’ and D, respectively, whereas the other indexes are assigned a value of ‘0’ using the others keyword.

Q_OUT <= (others => '0');

You can use the “others” keyword as a default assignment, regardless of the array size as shown in the third example.

WORD <= (A,B,C,D);

The fourth example is an exact opposite of the first one and it is valid.

H_BYTE <= (7|6|0|1 => '1', 2 to 5 => '0');

The fifth example assigns index 7,6,0, and 1 of H_BYTE to avalue of ‘1’ and index 2, 3, 4, and 5 of H_BYTE to ‘0’.

b. Record aggregates

Similar to array aggregates, record aggregates can also be used as shown here.

type D_WORD is record
    UPPER : std_logic_vector (7 downto 0);
    LOWER : std_logic_vector (7 downto 0);
end record;

signal DATA_WORD        : D_WORD;
signal H_BYTE, L_BYTE   : std_logic_vector (0 to 7);
 DATA_WORD <= (H_BYTE, L_BYTE);
 DATA_WORD <= (LOWER => L_BYTE, UPPER => H_BYTE);
 DATA_WORD <= (LOWER | UPPER => H_BYTE);
 DATA_WORD <= (others => H_BYTE);
 

DATA WORD here is a record having H_BYTE and L BYTE arrays of the std_logic type. Record can only accept array aggregates.