Basic VHDL Constructs

Purpose of this Lecture

  • VHDL Constructs
  • More Examples

  • Data Types

  • Assignments

  • Objects

  • Package and Libraries

  • Predefined Operators

  • Multi-Valued Logic

  • Data Types


    All declarations VHDL ports, signals and variables must specify their corresponding type or subtype. There are three defined data types in VHDL -

  • Access - pointers in other prog. language
    For example:
        TYPE node;
        TYPE pointer IS ACCESS node;
        TYPE node IS RECORD
    	data : INTEGER;
    	link : pointer;
        END RECORD;
  • Scalar - atomic units (integer, real, enumerated and physical)
  • Composite - arrays and/or records

    A type defines a set of values. A signal (or variable) with a particular type can only be assigned a value in the set of values that is defined for the type. We have used the type BIT in our examples without clarifying the definition of BIT. A signal with type BIT can take on either '0' or '1' as its value. ALso in our examples signals declare their types either in the PORT section of the ENTITY or before BEGIN in the ARCHITECTURE section. Signals and/or values and/or expression must have matching type at both side of the assignment ("<="). Now let us go back to what we have mentioned. There are 3 (or 4) predefined types. These types are defined in the source codes, and the VHDL language defines that they should be found in the package called STANDARD. We will go into detail of package later. For the time being these

    Predefined Types

    predefined types are:

    User defined types

    Having look at the predefined types in the language, let us look at how a user can define his/her own type in VHDL. As we have mentioned before there is a type called enumerated type in the language. A user defined type in VHDL is always an "enumerated type". Types are most commonly defined inside a package, architecture or process. Most synthesis tools are able to synthesize codes containing enumerated types. The following is the syntax for defining an enumerated type.

        TYPE my_type IS
    	(reset, idle, rw_cycle, int_cycle);
    It has the name of the type "my_state" and all possible values in this type as "reset", "idle", "rw_cycle", "int_cycle". Once a type is defined, we can assign signal to the defined type. It is important to remember that only values within the enumeration can be assigned to a signal with that particular type. For example:
          SIGNAL state : my_state;
          SIGNAL two_bit : BIT_VECTOR (0 TO 1);
        state <= reset;
        state <= "00";
        state <= two_bit;
    We have mentioned before that state can be encoded using binary representation. Most synthesis tools can build logic form a signal which is of an enumerated type. Usually the signal has the minimum number of bits required when it is represented in binary representation. In our previous example, these four states will be coded with "00", "01", "10", "11" in hardware implementation.


    A subtype is a type with a constraint. It is used to limit the range on the original type. It is used to guard the values of a signal of variable. The constraint put on a subtype could be NULL. In this case the subtype is the same as the base type. It just have a different name. Subtype declaration has the form: "SUBTYPE name_of_the subtype IS base_type range_limit". Here are some example of a SUBTYPE declaration:

    SUBTYPE decimal_digit IS INTEGER RANGE 0 TO 9;


    A signal is assigned a new value in VHDL with what is known as a "signal assignment statement", as we have seen in the examples of the half adder and full adder. An assignment to a signal defines a driver on that signal. The concept of the driver is exactly the same as an output of a logic gate drives a signal in real hardware. That is if a signal is assigned to another signal such as "A <= B", a buffer may or may not be created by the synthesis tool depending on the load at B.

    Multiple drivers

    Just as in hardware, if more than one assignment is made to a signal, then that signal has more than one drivers. In this case, the signal must be declared to be a special type, known as a resolved type. A resolved type has a function call associated with it to "resolve" the final value of the signal. All of the type we have mentioned so far are of unresolved type. So multiple assignments to the same signal is not allowed. For example you cannot do:
    SIGNAL A, B, C : bit;
    A <= B;
    A <= C;
    in the same architecture

    Array Assignments

    Two array objects can be assigned to each other, as long as they are of the same type and same size. It is important to note that the assignment is by position, and not by index number. There is no concept of a most significant bit defined within the language. It is strickly interpreted by the user who uses the array. Here are examples of array assignments:

    with the following declaration:
        SIGNAL z_bus: BIT_VECTOR (3 DOWNTO 0);
        SIGNAL a_bus: BIT_VECTOR (1 TO 4);
        z_bus <= a_bus;
    is the same as:
        z_bus(3) <= a_bus(1);
        z_bus(2) <= a_bus(2);
        z_bus(1) <= a_bus(3);
        z_bus(0) <= a_bus(4);
    Finally, it is possible to access a portion of the array (a slice) including a single element. The direction of the slice (i.e. to or downto) must match the direction in which the array is declared. For example:
    with the following declaration:
        SIGNAL z_bus: BIT_VECTOR (3 DOWNTO 0);
        SIGNAL a_bus: BIT_VECTOR (1 TO 4);
        z_bus(3 DOWNTO 2) <= "00";
        a_bus(2 TO 4) <= z_bus(3 DOWNTO 1);
        z_bus(2 To 3) <= "00";
    Finally, there are two more ways you can assign signal values in an array. These two methods are: concatenation and aggregates. VHDL provides the ability to associate single bits and vectors together to form array structures. This is known as concatenation and uses the ampersand (&) operator. The examples followed show that single bits, and bit_vectors can be concatenated together to form new vectors.
        SIGNAL z_bus : BIT_VECTOR(3 DOWNTO 0);
        SIGNAL a, b, c, d : BIT;
        SIGNAL byte : BIT_VECTOR(7 DOWNTO 0);
        SIGNAL a_bus : BIT_VECTOR(3 DOWNTO 0);
        SIGNAL b_bus : BIT_VECTOR(3 DOWNTO 0);
        z_bus <= a & b & c & d;
        byte  <= a_bus & b_bus;
        byte  <= a_bus & '0' & '0' & '0' & '1';
        byte <= a_bus & c;
    Another method of assigning to elements of an array is known as an aggregate. An aggregate is contained within round brackets and assignments to each element are seperated by commas (,). For example:
        SIGNAL z_bus : BIT_VECTOR(3 DOWNTO 0);
        SIGNAL a, b, c, d : BIT;
        SIGNAL byte : BIT_VECTOR(7 DOWNTO 0);
        z_bus <= (a, b, c, d);
    is the same as:
        z_bus(3) <= a;
        z_bus(2) <= b;
        z_bus(1) <= c;
        z_bus(0) <= d;
    it is possible to specify the element of an array through assignment by name and by position. The following example shows that a range of array can be specified, as long as the same value is being assigned to each element in the range.
    SIGNAL x : bit_vector(3 DOWNTO 0);
    SIGNAL a, b, c, d : BIT;
    SIGNAL byte : bit_vector(7 DOWNTO 0);
    x <= (3 => '1', 1 DOWNTO0 => '1', 2 => b);
    Lastly, aggregates have the ability to use the "OTHERS" reserved statement, which will assign a value to all other elements of the array that have not been specified. However, not all synthesis tools support the use of aggregates (as we have mentioned before that synthesis tools support only a subset of the language usually). Therefore you may be required to use concatenation to perform array manipulation.

    Objects and Classes

    There are four types of Objects in VHDL:
  • Signals
  • Constants
  • Variables
  • Files

    We have used signals in many examples previously. In a software language, a variable contains a value and can accept new values through assignment statement just like signals. However, variables are usually used to described the behavior while signals are used to represent the actual wires (connections) in hardware. Signals may have timing implied. Constants on the other hand have fixed values through out a program's execution. We call these classes of objects. Fro example a variable is an object whose class is variable.

    Packages and Libraries


    A package contains a collection of definitions that may be referenced by many designs at the same time. Placing definitions that are commonly used by several design in one location helps a design team to work more consistently. Figure that follows illustrates this concept:

           PACKAGE project_a IS
    	  --- definitions
           END project_a;
           /         |          |        \
           Entity A  Entity B   Entity C  Entity D
    A package may contain definitions of constant values, user defined datat types, component declaration of procedures (sub-programs).

    A package is a collection of types, constants, subprograms and possibly other things, usually intended to implement some particular service or to isolate a group of related items. In particular, the details of constant values and subprogram bodies can be hidden from users of a package, with only their interfaces made visible.

    A package may be split into two parts: a package declaration, which defines its interface, and a package body, which defines the deferred details. The body part may be omitted if there are no deferred details. The syntax of a package declaration is:

    The declarations define things which are to be visible to users of the package, and which are also visible inside the package body. (There are also other kinds of declarations which can be included, but they are not discussed here.)

    An example of a package declaration:

    In this example, the value of the constant vector_table_loc and the bodies of the two functions are deferred, so a package body needs to be given.

    The syntax for a package body is:

    Note that subprogram bodies may be included in a package body, whereas only subprogram interface declarations may be included in the package interface declaration.

    The body for the package data_types shown above might be written as:

    In this package body, the value for the constant is specified, and the function bodies are given. The subtype declarations are not repeated, as those in the package declarations are visible in the package body.

    Package Use and Name Visibility

    Once a package has been declared, items declared within it can be used by prefixing their names with the package name. For exam[ple we could use the previously defined package "data_types" by using the following statement before our entity statement assuming the package is compiled in the owrking directory.

    USE WORK.data_types.ALL;
    ENTITY ...


    Design units such as packages, architectures and Entities can be compiled into a library. Libraries are generally implemented as directories on a computing system. We have mentioned that a library is referenced by its logical name and there exists a mapping of logical name into the physical name of the system (which is the directory names on the computing system). The mapping is maintained by the system. Just like variables and signals, library must be declared before we can use it. This can be done with the following syntax:

    	LIBRARY logical_library_name_1, logical_library_name2, ...;
    In the VHDL language, the libraries STD and WORK are implicitly declared in the source code. User programs do not need to declare these two libraries. Library STD contains the standard packages with VHDL distribution. The WORK library refers to the current working directory. There are other libraries that comes with your tool. Usually, your tool comes with the implementation of IEEE 1164 standard library called IEEE. We will discuss this standard shortly after. Other libraries such as math and other misc. are often supplied with the tool.

    Predefined Operators

    Operators are used in expressions involving signal, variable or constant object types. The following are the types of operators as defined in the VHDL language.

    Each type of the operators are listed with higher precedence on the right. For example, among logical operators, AND, OR, NAND, NOR and XOR are of the same precedence while NOT has a higher precedence. The following example shows the simple use of logical operators. Note the brackets are required otherwise the NOT operator will only invert signal b.

        z < = a AND NOT (b or c);
    Logical operators are predefined for the following types - bit, bit_vector, boolean. And they are also predefined for std_ulogic, std_log, std_ulogic_vector and std_logic_vector. These four types will be discussed later. When logical operators are applied to a vector (array) the two operands must have the same length and the operation is performed on each pair of the position of the array.

    Relational operators returns a boolean value. They are most often used within an if-then-else statement to control the flow of code depending on conditions. The rule for using relational operators are the operands must be of the same type. For an array, the operands can be of different lengths. While they are of different lengths, the operands are aligned to the left and compared to the left. This makes "111" greater than "1011" when they are compared. That is the statement ' "111" > "1011" ' is TRUE. What it shows is that arrays (vectors) has no numerical meaning associated with them. They are just a collection of objects.

    Arithmetic operators include the addition, multiply and unary performs operations on type integer, real and time. Since a vector does not represent a value but a collection of object they can not be operated on by the arithmetic operator. We will later discuss how to avoid this problem. Again an arithmetic operator must operate on objects of the same type in general.

    None of the vector types can be operated on by Arithmetic Operators, including std_logic_vectors !!!!! You cannot do Z <= A * B; when A, B, Z are std_logic_vectors!!!

    As we have mentioned before, subtypes are useful in enforing the result to be within certain range. The following example illustrate this concept:

        ENTITY add IS
    	PORT (a, b : IN INTEGER RANGE 0 TO 7;
    	      z : OUT INTEGER RANGE 0 To 15);
    	END add;
    	ARCHITECTURE arithm OF add IS
    	    z <= a + b;
    	END arithm;
    This example shows the ability to specify that an integer should fall within a certain range. In this case, a, b, z are all considered as the same type, but the simulator will check that their values are within the range specified during run time. It will produce an error if they are not within range.

    One exception is allowed on type checking with arithmetic operations. That is the type time can be divided by or multiplied with a value of type integer or real and still retain the the type time. This is very useful in creating stimulus in VHDL. Here are some examples:

        SIGNAL clk : BIT;
        CONSTANT period : time := 50 ns;
        WAIT FOR 5 * period; -- 250 ns
        WAIT FOR period * 2 ; -- 100 ns
        clk <= NOT clk after period/2;
    As an exercise, let's look at an example and the answer can be found at the end of this page.
        Q: Which of the following statement(s) in the functional description of the
        architecture body is (are) correct (legal VHDL)?
        ARCHITECTURE exercise OF operators IS
    	SIGNAL bool : BOOLEAN;
    	SIGNAL a_int : INTEGER RANGE 0 TO 15;
    	SIGNAL b_int : INTEGER RANGE 0 TO 15;
    	SIGNAL z_int : INTEGER RANGE 0 TO 15;
    	SIGNAL z_bit : bit;
    	SIGNAL b_vec : bit_vector;
    	z_bit <= a_int = b_int;    -- statement 1
    	bool <= a_int > b_int;     -- statement 2
    	z_int <= b_int + "1010";   -- statement 3
    	z_int <= a_int + b_int;    -- statement 4

    Multi-Valued Logic

    In real life, a digital system may need more values than just '0' an d'1' to represent the signal value. In this section we will look at a standard called "Standard Logic" to allow us to represent logic systems with more than just values of '0' and '1'. This is a multi-valued logic system. You may have noticed that the type BIT we have been using has some limitation. It can only used to represent '0' and '1'. In simulation it can be useful to represent other values such as unknown, un-initialized, high impedance and different drive strengths. Also for synthesis, it can be useful to represent a DON'T care condition. It may simplify the logic being synthesized. (Clarify each conditions if you don't know already.) Multi-valued logic system or MVL system for short is useful in representing other values as we have mentioned. MVL is an enumerated type defining characters to represent these other values. Of course a set of functions must also be defined to operate on these values. Functions such as "and", "or" and "not" must be defined at least to make it complete. Until early 1992, there was no standard in this area. And each simulation and synthesis vendors used their own systems, ranging from ones with 4 values to ones with 126 values(mvl4, mvl7, mvl9, mvl12, mvl46 and mvl126)! This condition made sharing code developed on different toolset rather difficult. Fortunately a standard is established. This standard defining a MVL system with 9 values (or states) is called the IEEE standard number 1164. This is a standard seperated from the VHDL language standard. This standard 1164 is defined in pure VHDL source code and describes a datatype called "std_ulogic" (we called it 'standard' 'U' 'logic'). The following lists the nine possible values in this MVL system.

        TYPE std_ulogic IS (
    	'U',			-- uninitialized
    	'X',			-- unknown  (strong drive)
    	'0',			-- logic 0 (strong drive)
    	'1',			-- logic 1 (strong drive)
    	'Z',			-- high impedence
    	'W',			-- unknown (weak drive)
    	'L',			-- logic 0 (weak drive)
    	'H',			-- logic 1 (weak drive)
    	'-');			-- don't care
    The type std_ulogic is an unresolved data type and can only have one driver. In fact the 'u' in the name stands for "unresolved". There is another type defined called "std_logic", which is a resolved version of std_ulogic and can have more than one driver. The type std_logic has the same nine values as type "std_ulogic". These type definitions are contained in a package called standard_logic_1164. We will see how to reference this package later. In order to complete the mvl systems the package also contains the definition of arrays of std_logic and std_ulogic, known as std_logic_vector and std_ulogic_vector (corresponding to the bit_vector type). These types tend to be used to describe bus structures in the way in which we have used type bit_vector in earlier parts of the examples. Having looked at the definitions of the standard logic based data types, let us look at some examples of how they can be used. As we have mentioned that entities and packages can be compiled into a library. The types and functions described by IEEE-1164 standard is contained within a library called IEEE. Before we can use the types defined in IEEE we need to reference both the library and the package. You must use the following code before each ENTITY you write for your design that uses Standard Logic.
         LIBRARY IEEE;		    -- make library visible
         USE IEEE.Std_Logic_1164.all;   -- makes all contents of package available
         ENTITY mvls IS
    	 PORT (a, b : IN std_logic;
    	       z    : OUT std_logic);
         END mvls;
    It turns out that the two types we have mentioned std_ulogic and std_logic have an interesting property. It is possible to freely assign objects of type std_logic to any objects of type std_ulogic. The following examples are all legal.
        SIGNAL a, b, z : std_ulogic;
        SIGNAL res_z : std_logic;
        z <= a;
        res_z < = a;
        b < = res_z;
    Again we recall that std_logic is of a resolved type while std_ulogic is of an unresolved type. Only resolved type signals may have multiple drivers. Even though we have mentioned these two types in IEEE-1164 we recommend that you describe your design using only std_logic type.
    Answer to exercise 1:
        Statement 2 and 4 are correct statements.