In this post, we talk about the methods we can use to create our own custom data types in VHDL, including arrays and record types.
In the previous post we talked about the basic predefined types in VHDL. Whilst these are adequate for the majority of cases there are occasions when we require a custom data type in our code.
In VHDL, the most commonly used custom types are the enumerated types. These provide us with a technique for creating an entirely new type with custom values.
However, we can also create sub types which allow us to modify the range of values in one of the predefined VHDL types. In fact, the inbuilt VHDL positive and natural types are both example of subtypes which limit the range of values the integer can accept.
In addition to this, we can also use array and record types in our VHDL designs. This can provide us with a more structured design which is easier to maintain.
In the rest of this post we will at the methods we use to create all of these custom types in our VHDL code.
When we write VHDL code, there are instances when the predefined types we wish to create a new type. One of the most common use cases is creating an enumerated type which we us to implement finite state machines (FSM).
Actually there are two ways in which we can create a custom type in VHDL. We can either create an entirely new type or we can create a subtype.
Let's take a look at both of these methods.
It is possible for us to create an entirely new type to use in our VHDL design. To do this, we must create a new name for our type and then associate some valid values with it.
The code snippet below shows the general syntax we use to create an new type.
type <type_name> is (<list of values>);
The list of values is a comma separated list of all the values of our type can have.
When declaring a new type in VHDL we typically create an enumerated type. This means our list of values are just strings, or words, which we can assign to any instances of the type.
As an example, let's create a new type which we use to store the state of a small FSM. This is one of the most common reasons for creating a new type in VHDL.
For this example, our FSM will have just four states - idle, starting, runnning and stopping.
The code snippet below shows how we would create this type in VHDL. We can see that this is an enumerated type, meaning the list of values are simple strings.
type example_fsm_t is (idle, starting, running, stopping);
Once we have created this type, we can create an instance of it in our code. We can then assign any value to it which we have listed in the declaration.
The code snippet below shows how we would create a signal using our custom type and assign it to the idle state.
-- Declare a signal which uses our custom type
signal example : example_fsm_t;
-- Example of assigning a value to our signal
example <= stopping;
The second method we can use to create a custom type modifies one of the existing types. To do this, we use the subtype VHDL keyword and restrict the range of valid values which the new type can take.
The code snippet below shows the general syntax we use when creating a sub type.
subtype <type_name> is <type> range <valid_values>;
One of the most common uses for the sub type keyword in VHDL is to restrict the number of bits in an integer type.
As an example, we may want to declare an integer which only uses 8 bits. In this case we can declare a new subtype and limit the maximum value to 255. The code snippet below shows how we would do this.
subtype integer_8_bit is range 0 to 255;
After we have created a new subtype, we can create instances of it to use in our VHDL design. We can then assign it any of the values we specified in the declaration.
The code snippet below shows how we would create a signal using our new type.
signal example : integer_8_bit;
We can create our own array types in VHDL. To do this, we include the array keyword in the type definition. We must also declare the number of elements in the array.
The code snippet below shows the general syntax we use to declare an array type in VHDL.
type <type_name> is array (<range>) of <type>;
The <type> field in the above construct can accept any VHDL type, including custom types we have declared. This means we can also build multidimensional arrays by using array types in this field.
The <range> field in the above example can be built using the downto and to VHDL keywords which we have seen before.
However, we can also use two special constructs which effectively create an unconstrained array type. This allows us to define the size of the array whenever we declare a port or signal which uses it.
To do this, we use the natural <range > or positive <range> keywords in the <range> field. The difference between the two is that the natural <range> option allows for zero based array numbering whereas positive <range> doesn't.
The VHDL code below shows the general syntax we use to create unconstrained array types.
-- Create a zero based unconstrained array
type <type_name> is array (natural <range>) of <type>;
-- Create an unconstrained array
type <type_name> is array (positive <range>) of <type>;
Once we have declared a custom array type it can be used in an entity port, as a signal or a as a variable.
To demonstrate how we declare a custom array type, lets consider a basic example. For this example we will create an array of 8 bit std_logic_vector types.
In addition, we will not constrain the array when we declare it. Instead, we will use the natural <range> construct so that we can change the size as we declare signals.
The code snippet below shows how we declare and use our custom array type.
-- Declaration of the array type
type byte_array_type is array (natural <range>) of std_logic_vector(7 downto 0);
-- Example of a signal which uses the array type
-- This results in an array of 8 elements
signal example : byte_array_type(7 downto 0);
We can create more complex data types in VHDL using a record. Records can contain any number of different signals which we want to group together. These signals don't need to be of the same type.
We can think of records as being roughly equivalent to structs in C.
We often use records to simplify the port list in a VHDL entity. If we have a number of common signals, we can group them together in a record. We can then use this record as part of the entity which reduces the number of ports we require.
Using record types in an entity can also improve the maintainability of our code. The main reason for this is that we only need to manage the contents of a record in the place it is declared. Therefore, we can change connections in our ports just by modifying the record type. If our design features multiple modules which use the same record, this can reduce the effort require to modify connections between entities.
When we want to use a record in VHDL we must declare it as a type. We most commonly declare records inside a VHDL package. This allows us to use the record type in multiple different design files.
The code snippet below shows the general syntax we use to declare a record type in VHDL.
type <record_name> is record
-- We declare all the elements which make up the record here
<element_name> : <type>;
end record <record_name>;
After we have declared a record type, we can use it in the exact same manner as any other port or signal in our VHDL design. We can assign data to individual elements in the record or to the entire array.
The code snippet below shows the two methods we can use to assign data to the record.
-- Assigning data to all of the elements in a record
<signal_name> <= ( <element_name> => <value>,
<element_name> => <value>);
-- Assigning data to an individual element
<signal_name>.<element_name> <= <value>;
It is also possible for us to include records as elements in an array.
Lets consider a basic example to better demonstrate how a record type works in VHDL. For this example, we will write a record which contains all the signals required in a UART interface.
The UART interface consists of 4 different signals. Each of these signals are a single bit which means we can use a std_logic type to model them.
The code snippet below shows how we would declare this record type.
type uart_record_t is record
rx : std_logic;
cts : std_logic;
tx : std_logic;
rts : std_logic;
end record uart_record_t;
After we have created the record, we can then use it in the type field for any port or signal in our VHDL design.
The code snippet below shows hows we would declare a signal which uses our UART record type.
signal uart_example : uart_record_t;
Finally, we will obviously also want to drive data onto our record signal. The VHDL code snippet below gives some examples of how we can assign data to the record.
-- Set all the elements in the record to 0b
uart_example <= ( rx => '0',
cts => '0',
tx => '0',
rts => '0');
-- Set the tx signal to 1b
uart_example.tx <= '1';