In this article, we shall discuss data types in VHDL. After reading this article, you’ll be able to answer the following questions.
- What are data types?
- Why are they important?
- What are the different types of data types, and what are their subtypes?
Contents
What are data types?
Data types are just attributes attached to the data that helps the VHDL compiler in understanding how to treat that particular data. In simpler words, these are special commands that tell the VHDL compiler what something is and what it is supposed to do next.
In VHDL, we define datatypes while initializing signals, variables, constants, and generics.
Also, VHDL allows users to define their own data types according to their needs, and those are called user-defined data types.
User-defined data types can be defined in a separate file and shared as a library. You can define a bunch of custom data types and put them in a library and use that whenever you want. Sort of like your personal toolkit!
One of the popular standard libraries is from IEEE, which contains many useful and extensive data types, functions, and components declaration.
Due to its usefulness, it is the most popular and widely used library in VHDL. It has useful datatypes like std_logic
and std_ulogic
which helps us to make simulation much more practical. And to make our VHDL programming easy, we use IEEE’s library and its functions quite often.
Significance of datatypes
Let’s understand this with an analogy. Just imagine you want to store apples & oranges on a shelf. For which you decided to use cardboard boxes having a label of the fruit it consists.
Now, what if you have to store apple juice and orange juice? Now you can’t use cardboard boxes anymore, and you’ll need plastic or metallic watertight containers. So, the type of container depends on what we want to store in it.
Similarly, we store data values in variables of a suitable type. And the same analogy continues for other data objects like signals, constants, etc.
In VHDL, we have a variety of data types that were necessary to make simulations as practical as possible. All data types and their properties like the range of values it can accept are somewhere defined in a library.
When we talk about predefined datatypes in VHDL, we mean by the data types described in the standard library only. Let’s now look at all those datatypes.
A more technical example:
Consider a ‘signal Q’ whose datatype we don’t know yet. It is initialized with a value ‘0’. If we wish to change its value to ‘1’ later on. How we’ll do it?
You may say the below statement will do the job easily.
Q <= not Q
But wait and think, you don’t know yet if it is of type bit
, integer
, character
or any other.
As a programmer, you have the freedom to use a data type, but you should also utilize your wisdom to choose a suitable one.
Data types in the standard library
There are many data types defined in the standard library of VHDL. To make them easy to understand, we categorize them into the following four types:
- Enumerated type
- Numeric type
- Array
- Miscellaneous
Now let’s understand them all one by one.
Enumerated type
These datatypes can take several values listed/ enumerated in the standard library. Enumerated data types consist of the following types:
- Boolean
- Bit
- Character
- Severity level
Boolean data type
A Boolean
type data object can have a single value. Either ‘FALSE’ or ‘TRUE.’ That’s it. The logical operations that can be performed on Boolean variables are “and, or, not, nand, nor, xor.”
Initializing a Boolean
variable and a signal without an initial value:
variable DONE: BOOLEAN; signal enable: BOOLEAN;
Initializing a Boolean
variable and a signal with an initial value:
variable DONE: BOOLEAN:= FALSE; signal enable: BOOLEAN:= TRUE;
Bit data type
A Bit
type of data object can either have ‘0’ or ‘1’. The logical operations that can be performed on Bit variables are the same as of Boolean, i.e. “and, or, not, nand, nor, xor.”
Initializing a Bit
type variable and a signal without an initial value:
variable temp: BIT; signal CLK: BIT;
Initializing a Bit
type variable and a signal with an initial value:
variable temp: BIT := 1; signal CLK: BIT := 0;
Character data type
A character
type data object can hold any one character or special character at a time or can hold sum defined literal like NUL, SOX, STX, etc.
Initializing a character
variable without an initial value:
variable VAL: character;
Initializing a character
variable with an initial character:
variable VAL: character :=’$’;
All possible enumeration for a character data type
NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, HT, LF, VT, FF, CR, SO, SI, DLE, DCI, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FSP, GSP, RSP, USP, ' ' , ' ! ', ' ” ', ' # ', ' $ ', ' % ', ' & ', ' ’ ', ' (' , ' ) ', ' * ', ' + ', ' , ', ' - ', ' . ', ' / ', ' 0 ', ' 1 ', ' 2 ', ' 3 ', ' 4 ', ' 5 ', ' 6 ', ' 7 ', ' 8 ', ' 9', ' : ', ' ; ', ' < ', ' = ', ' > ', ' ? ', ' @ ', ' A ', ' B ', ' C ', ' D ', ' E ', ' F ', ' G ', ' H ', ' I ', ' J ', ' K ', ' L ', ' M ', ' N ', ' O ', ' P ', ' Q ', ' R ', ' S ', ' T ', ' U ', ' V ', ' W ', ' X ', ' Y ', ' Z ', ' [ ', ' \ ', ' ] ', ' ^ ', ' _ ', ' ` ', ' a ', ' b ', ' c ', ' d ', ' e ', ' f ', ' g ', ' h ', ' i ', ' j ', ' k ', ' l ', ' m ', ' n ', ' o ', ' p ', ' q ', ' r ', ' s ', ' t ', ' u ', ' v ', ' w ', ' x ', ' y ', ' z ', ' { ', ' | ', ' } ', ' ~ ', DEL
Severity levels
The severity level
data type can have the values ‘NOTE’, ‘WARNING,’ ‘ERROR,’ ‘FAILURE.’ Severity levels are used in assertion statements, which will be discussed in detail in an upcoming article, but, in short, they behave as logs (think of them like personal paper flags) while simulation to indicate if any error occurs and what is its severity.
When we initialize a data object without value, its default value is set to the leftmost listed value from the library. For example, in library enumeration for ‘BIT’ is
type bit is (‘0’, ‘1’);
So default value for data objects of datatype bit is ‘0’. And the same goes for all data types as mentioned in the table below
S. no. | Datatype | Default initial value |
1. | Boolean | FALSE |
2. | Bit | 0 |
3. | Character | NUL |
4. | Severity level | NOTE |
Numeric type
As the name suggests, these data types can hold numeric values only. Numeric data type consists of the following types:
- Integer
- Real
Integer data type
It can hold an integer number ranging from -(231 – 1) to +(231 – 1). Interestingly two subtypes of integers are also defined in the standard library of VHDL.
Now, what are subtypes you ask, in short, a subtype is a datatype which has constrained values of its base type. Sound confusing?
Here’s an example, consider the statement below
subtype NEW_INTEGER is INTEGER range 50 to 150;
This creates a subtype named ‘MY_INTEGER’ of base type ‘INTEGER’ with values ranging from 50 to 150.
Two subtypes that are defined in the standard library are
- ‘NATURAL’ having a range from 0 to +(231 – 1)
- ‘POSITIVE’ having a range of 1 to +(231 – 1).
Initialising a natural
variable
variable VALUE: natural :=2;
Initialising a positive
variable
variable VALUE: positive :=2;
You can also create your own subtype of any datatype using type
declaration, we will look into that in a future article.
Real data type
It can hold floating-point numbers ranging from -1.0E38 to +1.0E38. These are very helpful for precise calculation. For example, if we need to use the value of pi (π) for some calculations, then we can’t use an integer to store its value (3.14159). An integer can only store 3, which decreases preciseness of calculations. But a real
can store floating digits of up to 6 decimal places.
constant Pi : real := 3.14159;
Array data types
An array is a collection of objects of the same type. There are two arrays predefined in the standard library
- String
- Bit vector
String – A string is simply a 1-Dimensional array of characters.
variable MESSAGE: STRING(1 to 17) := "Technobyte";
Bit vector -A bit_vector
is simply an array of grouped bits, they are useful while defining multiple pin inputs. Consider an example. You are creating a 4-bit adder so you’ll need 4-bit inputs, rather than defining them individually you can use:
SIGNAL input: BIT_VECTOR (3 DOWNTO 0);
The above statement defines a 4-bit input. To access them individually, we can use input(0) to access the first bit, input(1) for the second, and so on. This also helps us while writing testbenches. Assigning values to vectors is way easier. Check out this example:
Input <= “0101”;
The statement above assigns ‘0’ to input(0), ‘1’ to input(1), and so on.
Miscellaneous
There are two data types in the standard library, i.e., ‘TIME.’ This is used to store values that can be further utilized for timing operation, like creating specific delays. Some units of time are also defined in the standard library, as shown below:
fs; -- femtosecond ps = 1000 fs; -- picosecond ns = 1000 ps; -- nanosecond us = 1000 ns; -- microsecond ms = 1000 us; -- microsecond sec = 1000 ms; -- seconds min = 60 secs; -- minutes hr = 60 min; -- hours
Initializing a time variable with an initial value
variable DELAY: time :=5 ns;
Data types from non-standard libraries
Enumerated type
These datatypes can take several values all listed/ enumerated in their respective library. Enumerated data types from popular libraries are:
Std_ulogic – We pronounce it as ‘standard u logic’ or ‘standard unresolved logic.’
We use it to represent much more practical details of digital signals in circuits and wires.
All enumeration for a Std_ulogic
data type is listed below
'U'
: Uninitialized.
'X'
: Unknown. Impossible to determine this value/result.
'0'
: logic 0
'1'
: logic 1
'Z'
: High Impedance
'W'
: Weak signal, can’t tell if it should be 0 or 1.
'L'
: This signal is also weak that it should probably go to 0
'H'
: This signal is also weak but it should probably go to 1
'-'
: Don’t care.
Initializing a Std_ulogic
type variable and a signal without an initial value-
variable temp: std_ulogic; signal CLK: std_ulogic;
Initializing a Std_ulogic
type variable and a signal with an initial value-
variable temp: std_ulogic := 1; signal CLK: std_ulogic := 0;
library IEEE
and should utilize the clause use IEEE.std_logic_1164.all
std_logic – This datatype has the same enumeration as of std_ulogic
. However, the difference is that std_logic
is resolved and we don’t need extra resolution function.
All enumeration for a Std_ulogic
data type is listed below
'U'
: uninitialized. Any value is not assigned to the signal yet.
'X'
: unknown. Impossible to determine this value/result.
'0'
: logic 0
'1'
: logic 1
'Z'
: High Impedance
'W'
: Weak signal, can’t tell if it should be 0 or 1.
'L'
: This signal is also weak that it should probably go to 0
'H'
: This signal is also weak but it should probably go to 1
'-'
: Don’t care.
Initializing a Std_logic
type variable and a signal without an initial value:
variable temp: std_logic; signal CLK: std_logic;
Initializing a Std_logic
type variable and a signal with an initial value:
variable temp: std_logic := 1; signal CLK: std_logic := 0;
Array type
Std_ulogic_vector – A mentioned earlier, an array is a collection of objects of the same type. So here also when we want to initialize a multi-bit input, we use vector notation to create a vector of multiple std_ulogic
bits.
SIGNAL Address: STD_ULOGIC_VECTOR(3 DOWNTO 0);
The above statement defines a 4-bit input. To access them individually, we can use input(0) to access the first bit, input(1) for the second, and so on. This also helps us while writing testbenches. Assigning values to vectors is way easier, see example-
Address <= “0101”;
The statement above assigns ‘0’ to input(0), ‘1’ to input(1), and so on.
Std_logic_vector
This is also similar to other vector datatypes; therefore, its initialization and assignment operators are the same.
Initializing a std_logic_vector
type signal array-
SIGNAL Address: STD_LOGIC_VECTOR(3 DOWNTO 0);
Value assignment to the signal array-
Address <= “0101”;
Miscellaneous
Record – It is a special datatype, and we use it to club many data objects of the same or different data types. You can also understand it as a user-defined datatype. It is analogous to structure in C programming language.
Example:
type MODULE is record SIZE: INTEGER range 20 to 200; DELAY: TIME; NO_OF_INPUTS: BIT; NO_OUTPUTS: BIT; end record;
In the code above, we have created a record
named ‘MODULE,’ and it has 4 data objects inside it. Now MODULE
is a datatype in itself.
SIZE of type INTEGER
and can only accept values in the range of 50-100.
DELAY of type TIME
NO_OF_INPUTS of type BIT
.
NO_OF_OUTPUTS of type BIT
.
Initializing a record
variable AND_COMP: MODULE;
Above line of code, creates a data object of type MODULE
named AND_COMP
Now, assigning values to a record
NAND_COMP := (50, 20 ns, 3,2);
The above line of code assigns “50” to SIZE, “20 ns” to DELAY, “3” to NO_OF_INPUTS, and “2” to NO_OF_OUTPUTS.
You may assign value to each element separately.
NAND_COMP.DELAY := 10 ns; NAND_COMP.NO_OF_INPUTS := 4;
Examples
Let’s see some examples of VHDL, and we will focus on the datatypes that we are using.
Bit and bit_vector
We will be talking only about the program of 2×1 multiplexer, so if you are interested in working on it, then we have a separate article here.
library IEEE; use ieee.std_logic_1164.all; entity mux is port ( input : in bit_vector(1 downto 0); sel : in bit; output : out bit); end mux; architecture behavioral of mux is begin process (sel) begin case sel is when "0" => output <= input(0); when "1" => output <= input(1); when others => output <= '0'; end case; end process; end behavioral;
In the above code, three ports are initialized, two inputs, and one output. For data input in 2×1 mux, we need 2-bit input we may use two different bits as
input1, input2 in bit;
To assign values to both will take two lines of code as
input1 <= '1'; input2 <= '1';
But this is not efficient, so we used bit_vector
notation and used the following code
input : in bit_vector(1 downto 0);
This creates 2-bit input as input(0) and input(1) and assigning values to them is way more comfortable as
input <= "11";
One may argue that it is just a matter of one line, so you should also think that it is also just a 2×1 mux. For instance, if we are programming a 32×1 mux, then, just for providing input, we would have needed 32 lines of code. But using bit_vector that can also be summed up in one line.
To summarize, we have studied all the standard predefined data types in the VHDL language. After that, we have discussed all the popular non-standard data types. For all data types, we have discussed their initialization and assignments syntaxes and their use cases.
As always, if you have any queries, we would love to address them. Just drop in a comment in the comments section below.