The VHDL 1076 specification describes four classes of data types:

 

•    Scalar types represent a single numeric value or, in the case of enumerated types, an enumeration value. The standard types that fall into this class are integer, real (floating point), physical, and enumerated types. All of these basic types can be thought of as numeric values.

 

•    Composite types represent a collection of values. There are two classes of composite types: arrays containing elements of the same type, and records containing elements of different types.

 

•    Access types provide references to objects in much the same way that pointer types are used to reference data in software programming languages.

 

•    File types reference objects (typically disk files) that contain a sequence of values.

 

Each type in VHDL has a defined set of values. For example, the value of an integer data type has a defined range of at least -2147483647 to +2147483647. In most cases you will only be interested in a subset of the possible values for a type, so VHDL provides the ability to specify a constraint whenever an object of a given type is declared. The following declaration creates an object of type integer that is constrained to the positive values of 0 to 255:

 

  signal ShortInt: integer range 0 to 255;

 

VHDL also provides a feature called a subtype, allowing you to declare an alternate data type that is a constrained version of an existing type. For example, the declaration

 

  subtype SHORT integer range 0 to 255;

 

 

creates an alternate scalar type with a constrained range. Because SHORT is a subtype of integer, it carries with it all operations available for the integer base type.

 

The four classes of data types are discussed in more detail below.

 

Scalar types

Scalar types are those types that represent a single value, and are ordered in some way so that relational operations (such as greater than, less than, etc.) can be applied to them. These types include the obvious numeric types (integer and real) as well as less obvious enumerated types such as Boolean and Character.

 

Bit type

The bit data type is the most fundamental representation of a wire in VHDL. The bit type has only two possible values, ‘0’ and ‘1’, that can be used to represent logical 0 and 1 values (respectively) in a digital system. The following example uses bit data types to describe the operation of a full adder:

 

entity fulladder is

    port (X: in bit;

          Y: in bit;

          Cin: in bit;

          Cout: out bit;

          Sum: out bit);

end fulladder;

 

architecture concurrent of fulladder is

begin

 

    Sum <= X xor Y xor Cin;

    Cout <= (X and Y) or (X and Cin) or (Y and Cin);

 

end concurrent;

 

The bit data type supports the following operations: and, or, nand, nor, xor, xnor, not, =, /=, <, <=, >, and >=.

 

Note: The IEEE 1164 specification describes an alternative to bit called std_ulogic. Std_ulogic has nine possible values, allowing the values and states of wires (such as high-impedence, unknown, etc.) to be more accurately described. (See Chapter 4, Using Standard Logic.)

 

Boolean type

The Boolean type has two possible values, True and False.  Like the bit data type, the Boolean type is defined as an enumerated type. The Boolean type does not have any implied width; it is simply the result of a logical test (such as a comparison operation or an if statement) or the expression of some logical state (such as in the assignment, ErrorFlag <= True;).

 

Integer type

An integer type includes integer values in a specified range. The only predefined integer type is integer. Integer types have a minimum default range of -2147483647 to +2147483647, inclusive. However, you can restrict that value with a range constraint and/or declare a new integer subtype with a range constrained range, as in the following example:

 

subtype byteint integer range 0 to 255;

 

The predefined subtype natural restricts integers to the range of 0 to the specified (or default) upper range limit.  The predefined subtype positive restricts integers to the range of 1 to the specified (or default) upper range limit.

 

An alternative to the integer data type is provided with IEEE Standard 1076.3. This standard defines the standard data types signed and unsigned, which are array types (based on the IEEE 1164 9-valued data types) that have properties of both array (composite) and numeric (scalar) data types. Like an array, you can perform shifting and masking operations on them and, like integers, you can perform arithmetic operations on them. More information about the IEEE 1076.3 data types can be found in the section Using Standard Logic.

 

Real (floating point) types

Floating point types are used to approximate real number values. The predefined floating point type provided in VHDL is called real. It has possible values in the range of at least -1.0E38 to +1.0E38.

 

The following declaration decribes a signal of type real that has been initialized to a real value of 4589.3:

 

  signal F0: real := 4589.3;

 

The real data type supports the following operations: =, /=, <, <=, >, >=, +, -, abs, +, -, *, and /.

 

Note: Floating point types have little use in synthesizable designs, as no synthesis tool available today will accept them.

 

Character type

VHDL’s character data type is similar to the character types you might be familiar with from software languages. Characters can be used to represent string data (such as you might use in a test bench), to display messages during simulation, or to represent actual data values in your design description. Unlike many software languages, character values in VHDL have no explicit value. This means that they cannot be simply mapped onto numeric data types or assigned directly to arrays of bits.

 

There is no specific numeric value associated with a given character literal in VHDL. (You cannot, for example, assign a character literal to an 8-bit array without providing a type conversion function that assigns unique array values—such as ASCII values—to the target array for each character value.)

 

The character data type is an enumerated type. However, there is an implied ordering (refer to the IEEE 1076-1993 specification for details).

 

Severity_level type

Severity_level is a special data type used in the report section of an assert statement. There are four possible values for an object of type severity_level: note, warning, error and failure. You might use these severity levels in your test bench, for example, to instruct your simulator to stop processing when an error (such as a test vector failure) is encountered during a simulation run. The following assert statement makes use of the FAILURE severity level to indicate that the simulator should halt processing if the specified condition evaluates false:

 

    assert (error_flag = ‘1’)

        report "There was an error; simulation has halted."

        severity FAILURE;

 

Time and other physical types

Time is a standard data type that falls into the catagory of physical types in VHDL. Physical types are those types that are used for measurement. They are distinquished by the fact that they have units of measure, such as (in the case of time) seconds, nanoseconds, etc.  Each unit in the physical type (with the exception of the base unit) is based on some multiple of the preceding unit. The definition for type time, for example, might have been written as follows (the actual definition is implementation-dependent):

 

    type time is range -2147483647 to 2147483647

           units

                  fs;

                  ps  = 1000 fs;

                  ns  = 1000 ps;

                  us  = 1000 ns;

                  ms  = 1000 us;

                  sec = 1000 ms;

                  min = 60 sec;

                  hr  = 60 min;

           end units;

 

Enumerated types

As we have seen, enumerated types are used to describe (internally) many of the standard VHDL data types. You can also use enumerated types to describe your own unique data types. For example, if you are describing a state machine, you might want to make use of an enumerated type to represent the various states of the machine, as in the following example:

 

architecture FSM of VCONTROL is

    type states is (StateLive,StateWait,StateSample,StateDisplay);

    signal current_state, next_state: states;

begin

   . . .

    -- State transitions:

    STTRANS: process(current_state,Mode,VS,ENDFR)

    begin

        case current_state is

            when StateLive =>    -- Display live video on the output

             . . .

           when StateWait =>    -- Wait for vertical sync

             . . .

            when StateSample =>  -- Sample one frame of video

            . . .

           when StateDisplay => -- Display the stored frame

             . . .

       end case;

    end process;

end FSM;

 

In this example (the control logic for a video frame grabber, described in detail in the section Sequential Statements), an enumerated type (states) is defined in the architecture, and two signals (current_state and next_state) are declared for use in the subsequent state machine description. Using enumerated types in this way has two primary advantages: first, it is very easy to debug a design that uses enumerated types, because you can observe the symbolic type names during simulation; second, and perhaps more importantly for this state machine description, you can defer the actual encoding of the symbolic values until the time that you implement the design in hardware.

 

Synthesis tools generally recognize the use of enumerated types in this way and can perform special optimizations, assigning actual binary values to each symbolic name during synthesis. Synthesis tools generally also allow you to override the encoding of enumerated data types, so you have control over the encoding process.

 

Composite types

 

Data Type

Values

Comment

bit_vector

"00100101", "10", etc.

Array of bit

string

"Simulation failed!", etc.

Array of characters

records

Any collection of values

User defined composite data type

 

Composite types are collections of one or more types of values. An array is a composite data type that contains items of the same type, either in a single dimension (such as a list of numbers or characters) or in multiple dimensions (such as a table of values). Records, on the other hand, define collections of possibly unrelated data types. Records are useful when you need to represent complex data values that require multiple fields.

 

Array types

An array is a collection of one or more values or objects of the same type. Arrays are indexed by a number that falls into the declared range of the array.

 

The following is an example of an array type declaration:

 

        type MyArray is array (15 downto 0) of std_ulogic;

 

This array type declaration specifies that the new type MyArray contains 16 elements, numbered downward from 15 to 0. Arrays can be given ranges that decrement from left to right (as shown) or increment (using the to keyword instead of downto). Index ranges do not have to begin or end at zero.

 

The index range (in this case 15 downto 0) is what is known as the index constraint. It specifies the legal bounds of the array. Any attempt to assign values to, or read values from, an element outside the range of the array will result in an error during analysis or execution of the VHDL design description.

 

The index constraint for an array can specify an unbounded array using the following array range syntax:

 

    type UnboundedArray is array (natural range <>) of std_ulogic;

 

This array type declaration specifies that the array UnboundedArray will have a index constraint matching the range of integer subtype natural, which is defined as 0 to the highest possible integer value (at least 2,147,483,647).

 

An array type is uniquely identified by the types (and constraints) of its elements, the number of elements (its range), and the direction and order of its indices.

 

Arrays can have multiple indices, as in the following example:

 

  type multi is array(7 downto 0, 255 downto 0) of bit;

 

Note: Multidimensional arrays are not generally supported in synthesis tools. They can, however, be useful for describing test stimulus, memory elements, or other data that require a tabular form.

 

The following example (a parity generator) demonstrates how array elements can be accessed, in this case within a loop:

 

entity parity10 is

    port(D: in array(0 to 9) of bit;

         ODD: out bit);

    constant WIDTH: integer := 10;

end parity10;

 

architecture behavior of parity10 is

begin

    process(D)

        variable otmp: Boolean;

    begin

        otmp := false;

        for i in 0 to D’length - 1 loop

            if D(i) = ‘1’ then

                otmp := not otmp;

            end if;

        end loop;

        if otmp then

            ODD <= ‘1’;

        else

            ODD <= ‘0’;

        end if;

    end process;

end behavior;

 

The direction of an array range has an impact on the index values for each element. For example, the following declarations:

 

    signal A: bit_vector(0 to 3);

    signal B: bit_vector(3 downto 0);

 

create two objects, A and B, that have the same width but different directions. The aggregate assignments:

 

    A <= (‘1’,’0',’1',’0');

    B <= (‘0’,’1',’0',’1');

 

are exactly identical to the assignments:

 

A(0) <= ‘1’;

A(1) <= ‘0’;

A(2) <= ‘1’;

A(3) <= ‘0’;

 

B(3) <= ‘0’;

B(2) <= ‘1’;

B(1) <= ‘0’;

B(0) <= ‘1’;

 

In this case, the arrays have the same contents when viewed in terms of their array indices. Assigning the value of B to A, as in:

 

    A <= B;

 

which would be exactly equivalent to the assignments:

 

    A(0) <= B(3);

    A(1) <= B(2);

    A(2) <= B(1);

    A(3) <= B(0);

 

The leftmost element of array A has an index of 0, while the leftmost value of array B has an index value of 1.

 

Record types

A record is a composite type that has a value corresponding to the composite value of its elements. The elements of a record may be of unrelated types. They may even be other composite types, including other records. You can access data in a record either by referring to the entire record (as when copying the contents of one record object to another record object), or individually by referring to a field name. The following example demonstrates how you might declare a record data type consisting of four elements:

 

    type data_in_type is

        record

            ClkEnable: std_logic;

            Din: std_logic_vector(15 downto 0);

            Addr: integer range 0 to 255;

            CS: std_logic;

        end record;

 

The four names, ClkEnable, Din, Addr and CS are all field names of the record, representing data of specific types that can be stored as a part of the record. For example, an object of type data_in_type could be created and initialized with the following signal declaration:

 

    signal test_record: data_in_type := (‘0’, "1001011011110011", 165, ‘1’);

 

This initialization would be identical to the assignments:

 

    test_record.ClkEnable <= ‘0’;

    test_record.Din <= "1001011011110011";

    test_record.Addr <= 165;

    test_record.CS <= ‘1’;

 

Records types are not generally synthesizable; however, they can be very useful when describing test stimulus. Examples shown in Chapter 9, Writing Test Benches, show how records can be used in combination with arrays to organize test stimulus.

 

Access and incomplete types

Access types and incomplete types are used to create data indirection in VHDL. You can think of access types as being analogous to pointers in software programming languages such as C or Pascal. Incomplete types are required to create recursive types such as linked lists, trees and stacks. Access and incomplete types can be useful for creating dynamic representations of data (such as stacks), but they are not supported in today’s synthesis tools. Refer to the IEEE VHDL Language Reference Manual for more information about these language features.

 

File types

File types are very useful for writing test benches. File types differ in the VHDL 1076-1987 and 1076-1993 specifications. Discussions and examples of each are presented below.

 

VHDL 1076-1987 file types

A file type is a special type of variable that contains sequential data. In the 1987 VHDL standard language, files are implicitly opened when they are declared, and it is not possible to explicitly close them. Objects of type file can be read from and written to using functions and procedures (read, write, and endfile) that are provided in the standard library. Additional functions and procedures for formating of data read from files is provided in the Text I/O library, which is also part of the 1076 standard. The built-in functions available for reading and writing files in VHDL (the 1987 specification) are:

 

Read(f, object)—Given a declared file and an object, read one field of data from the file into that object. When the read procedure is invoked, data is read from the file and the file is advanced to the start of the next data field in the file.

 

Write(f, object)—Given a declared file and an object, write the data contained in the object to the file.

 

Endfile(f)—Given a declared file, return a boolean true value if the current file marker is at the end of the file.

 

Files in VHDL are sequential; there is no provision for opening a file and reading from a random location in that file, or for writing specific locations in a file.

 

To use an object of type file, you must first declare the type of its contents, as shown below:

 

  type file_of_characters is file of character;

 

This declaration creates a new type, called file_of_characters, that consists of a sequence of character values. To use this file type, you would then create an object of type file_of_characters, as shown below:

 

  file testfile: file_of_characters is in "TESTFILE.ASC";

 

This statement creates the object testfile and opens the indicated disk file. You can now use the built-in read procedure to access data in the file. A complete architecture that loops through a file and reads each character is shown below:

 

architecture sample87 of readfile is

begin

    Read_input: process  

        type character_file is file of character;

        file cfile: character_file is in "TESTFILE.ASC";

        variable C: character;

        variable char_cnt: integer := 0;

    begin

 

        while not endfile(cfile) loop

            read (cfile, C) ;    -- Get a character from cfile into C

            char_cnt = char_cnt + 1;  -- Keep track of the number of

        -- characters

        end loop;

    end process;

end sample87;

 

VHDL 1076-1993 file types

In VHDL ’93, file types and associated functions and procedures were modified to allow files to be opened and closed as needed. In the 1987 specification, there is no provision for closing a file, and problems can arise when it is necessary for two parts of the same design description to open the same file at different points, or when existing files must be both read from and written to (as when appending data). The built-in functions available for file operations in VHDL ’93 are:

 

File_open(f, fname, fmode)—Given a declared file object, file name (a string value) and a mode (either READ-MODE, WRITE_MODE, or APPEND_MODE), open the indicated file.

 

File_open(status, f, fname, fmode)—Same as above, but return the status of the file open request in the first parameter, which is of type file_open_status. The status returned is either OPEN_OK (meaning the file was successfully opened), STATUS_ERROR (meaning the file was not opened because there was already an open file associated with the file object), NAME_ERROR (meaning there was a system error related to the file name specified) or MODE_ERROR (meaning that the specified mode is not valid for the specified file).

 

File_close(f)—Close the specified file.

 

Read(f, object)—Given a declared file and an object, read one field of data from the file into that object. When the read procedure is invoked, data is read from the file and the file is advanced to the start of the next data field in the file.

 

Write(f, object)—Given a declared file and an object, write the data contained in the object to the file.

 

Endfile(f)—Given a declared file, return a boolean true value if the current file marker is at the end of the file.

A complete architecture that opens a file and loops through it, reading each character in the file, is shown below:

 

architecture sample93 of readfile is

begin

    Read_input: process  

        type character_file is file of character;

        file cfile: character_file;

        variable C: character;

        variable char_cnt: integer := 0;

    begin

        file_open(cfile, "TESTFILE.ASC", READ_MODE);

        while not endfile(cfile) loop

            read (cfile, C) ;    -- Get a character from cfile into C

            char_cnt = char_cnt + 1;  -- Keep track of the number of

        -- characters

        end loop;

        file_close(cfile);

    end process;

end sample93;