View Course Path

Data Types in VHDL

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?

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:

  1. Enumerated type
  2. Numeric type
  3. Array
  4. 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:

  1. Boolean
  2. Bit
  3. Character
  4. 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:

  1. Integer
  2. 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

  1. ‘NATURAL’ having a range from 0 to +(231 – 1)
  2. ‘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

  1. String
  2. 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;
This datatype is defined in IEEE’s library.To use it you have to include 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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Top