Introduction to VHDL



VHDL Companion


In the field of Programmable Logic Devices (PLD), larger units and standard programming
languages have emerged to the forefront. Devices like Field Programmable Gate Arrays (FPGA) and Complex
Programmable Logic Devices (CPLD)
have made it possible to design and implement complete processing systems onto
one or two integrated circuit devices. A language to describe the logic hardware was developed to facilitate this design process.
That language is VHDL (Very high speed integrated circuit (VHSIC) Hardware Description Language )

Several manufactures of FPGA and CPLD devices as well as third party companies have created application programs
using VHDL to aid in designing and implementing these custom logic systems. Among the leaders in the field and their
associated software applications are:


Each of these has a University program for supporting schools that wish to teach VHDL and use practical projects
for lab experiments. In addition, these companies offer training support for individuals and instructors to assist in
learning about their software and device products. You can contact each of them at the links in the list above.

professor

Introduction to VHDL Chapter Highlights

Place HolderINTRODUCTION Place HolderDATA TYPES Place HolderENTITY BLOCK
Place HolderARCHITECTURE BLOCK Place HolderPROCESS Place HolderCONDITIONAL STATEMENTS
Place HolderLOOPS place holderSTRUCTURAL DESIGN place holderCOMPONENTS
place holderSIGNALS place holderMODULAR DESIGN Place HolderFUNCTIONS
place holderPROCEDURE place holderSTATE WIZZARD Place HolderLIBRARY FILES
Place HolderMULTIPLE ARCHITECTURES Place HolderLAB PAGES Place HolderREFERENCE
Place HolderEXAMPLE FILESPlace HolderVHDL Questionsplace holderVHDL Slides

  1. Introduction


    VHDL is an acronym for Very high speed integrated circuit (VHSIC) Hardware Description Language which is a programming language that describes a logic circuit by function, data flow behavior, and/or structure. This hardware description is used to configure a programmable logic device (PLD), such as a field programmable gate array (FPGA), with a custom logic design. The general format of a VHDL program is built around the concept of BLOCKS which are the basic building units of a VHDL design. Within these design blocks a logic circuit of function can be easily described.

    A VHDL design begins with an ENTITY block that describes the interface for the design. The interface defines the input and output l1ogic signals of the circuit being designed. The ARCHITECTURE block describes the internal operation of the design. Within these blocks are numerous other functional blocks used to build the design elements of the logic circuit being created.

    After the design is created, it can be simulated and synthesized to check its logical operation. SIMULATION is a bare bones type of test to see if the basic logic works according to design and concept. SYNTHESIS allows timing factors and other influences of actual field programmable gate array (FPGA) devices to effect the simulation thereby doing a more thorough type of check before the design is committed to the FPGA or similar device.

    Many software packages used for VHDL design also support schematic capture which takes a logic schematic or state diagram and translates it into VHDL code. This, in turn, makes the design process a lot easier. However, to fine tune any design, it helps to be familiar with the actual VHDL code.

    Return to Index.

  2. Data Types


    VHDL is a very strongly typed language. It does not allow a lot of intermixing of data types. The idea here is that since you are describing a piece of hardware, you need to keep things like signals and numbers separate. We shall start by looking at the different types of data that can be used with VHDL which include bits, buses, boolean, strings, real and integer number types, physical, and user defined enumerated types.

    Defining Signals


    There are two data types used for defining interfacing and interconnecting signals - bits and bit_vectors. The bit type defines a single binary bit type of signal like RESET or ENABLE. It is used anytime you need to define a single control or data line. For multiple bus signals, such as data or address buses, an array called a bit_vector is used. Bit_vectors require a range of bits to be defined and has the syntax: bit_vector(range)

    The range for a bit_vector is defined from the least significant bit (LSB) to the most significant bit (MSB) and can be set to go from one to the other in ascending or descending order by using: LSB to MSB or MSB downto LSB. Here are some examples of bit_vector forms:

    addressbus(0 to 7);
    databus(15 downto 0);

    The first defines an 8-bit address bus from addressbus(0) to addressbus(7). The second, a data bus from databus(15) downto databus(0).

    The IEEE STD_LOGIC_1164 standard includes additional definitions for VHDL data types. For the bit type, the IEEE type is STD_LOGIC and for a bit_vector it is STD_LOGIC_VECTOR. The use of the IEEE standard types assures that your VHDL code will be portable. That is it can be used by any vendors implementation software. Bit and bit-vector are types which are not universally accepted and may not be recognized by some application programs.

    The Boolean Type


    The boolean type has only two values: TRUE (1) and FALSE (0) and is usually used to hold the results of a comparison or the basis for conditional statement results.

    Numerical Types


    Number types that are usable in VHDL code are INTEGERS and REALS. Integers are signed numbers and reals are used for floating point values. The range of values for both number types is somewhat dependent on the software application being used.

    Subtyping


    VHDL provides a method to create a version of an existing type with a specified range of values by using the SUBTYPE declaration. A typical example of the use and syntax of this operation is:

    subtype SHORTINT is integer range 0 to 255; which creates an integer type, SHORTINT with a specified range of values from 0 to 255. This is NOT a new or enumerated (user) type which we shall describe next, but rather a modified existing type.

    Enumerated or User Data Type


    An enumerated data type provides a means for creating and defining user types. They are declared using the TYPE operator with a syntax of:

    TYPE type_name (type values);

    Once the data type has been declared, then it can be used in a variable declaration (discussed later). For example, here is a declaration for a data type called MONTHS:

    TYPE MONTHS (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC);

    A variable declared to be of type MONTHS can have anyone of the twelve values indicated in the parenthesis.

    Other Data Types


    VHDL specifications include additional data types that are used in the behavioral description of a circuit design. These types are:

    1. Arrays are single or multidimensional enumerated array types and the std_logic_vector type.
    2. An access type acts like a pointer type and has limited use.
    3. A file type is used to access a file.
    4. A physical type is used to specify finite quantities such as time, voltage, etc. This type includes units of measure such as milliseconds (ms) and volts.
    5. Time units used with the physical type are:
      • primary unit is fs................femtosecond
      • ps = 1,000 fs....................picosecond
      • ns = 1,000 ps...................nanosecond
      • us = 1,000 ns...................microsecond
      • ms = 1,000 us..................millisecond
      • sec = 1,000 ms................second
      • min = 60 sec....................minute
      • hour = 60 min...................hour
    6. The line type is an ASCII string of characters.
    7. A record contains a collection of multiple data types.

    This has been a summary of the data types used by VHDL. As we progress, we will see how most are implemented in VHDL design code.

    Return to Index.

  3. ENTITY BLOCK


    An entity block is the beginning building block of a VHDL design. Each design has only one entity block which describes the interface signals in to a nd out of the design unit. The syntax for an entity declaration is:

    entity entity_name is
        port (signal_name,signal_name : mode type;
             signal_name,signal_name : mode type);
    end entity_name;

    An entity block starts with the reserve word entity followed by the entity_name. Names and identifiers can contain letters, numbers, and the under score character, but must begin with an alpha character. Next is the reserved word is and then the port declarations. The indenting shown in the entity block syntax, is used for documentation purposes only and is not required since VHDL is insensitive to white spaces.

    A single PORT declaration is used to declare the interface signals for the entity and to assign MODE and data TYPE to them. If more than one signal of the same type is declared, each identifier name is separated by a comma. Identifiers are followed by a colon (:), mode and data type selections.

    In general, there are five types of modes, but only three are frequently used. These three will be addressed here. They are in, out, and inout setting the signal flow direction for the ports as input, output, or bidirectional. Signal declarations of different mode or type are listed individually and separated by semicolons (;). The last signal declaration in a port statement and the port statement itself are terminated by a semicolon on the outside of the port's closing parenthesis.

    In VHDL, ALL STATEMENTS are terminated by a semicolon.

    The entity declaration is completed by using an end operator and the entity_name. Optionally, you can also use an end entity statement. Here is an example of an entity declaration for a set/reset (SR) latch:

    entity latch is
        port (s,r : in std_logic;
              q,nq : out std_logic);
    end latch;

    The set/reset latch has input control bits s and r which are defined as single input bits and output bits q and nq. Notice that the declaration does not define the operation yet, just the interfacing input and output logic signals of the design. A design circuit's operation will be defined in the architecture block.

    You can define a literal constant to be used within an entity with the generic declaration, which is placed before the port declaration within the entity block. Generic literals than can be used in port and other declarations. This makes it easier to modify or update designs. For instance if you declare a number of bit_vector bus signals, each eight bits in length, and at some future time you want to change them all to 16-bits, you would have to change each of the bit_vector range. However, by using a generic to define the range value, all you have to do is change the generic's value and the change will be reflected in each of the bit_vectors defined by that generic. The syntax to define a generic is:

    generic (name : type := value);

    The reserved word generic defines the declaration statement. This is followed by an identifier name for the generic and a colon. Next is the data type and a literal assignment value for the identifier. := is the assignment operator that allows a literal value to be assigned to the generic identifier name. This operator is used for other assignment functions as we will see later.

    For example, here is the code to define a bus width size using a generic literal.

    entity my processor is generic (busWidth : integer := 7);

    Presently, busWidth has the literal value of 7. This makes the documentation more descriptive for a vector type in a port declaration:

    port( data_bus : in std_logic_vector (busWidth downto 0);
    	 q-out : out std_logic_vector (busWidth downto 0));
    In this example, data_bus and q_out have a width of eight (8) bits ( 7 down to 0). When the design is updated to a larger bus size of sixteen (16) bits, the only change is to the literal assignment in the generic declaration from 7 to 15.

    Return to Index.

  4. ARCHITECTURE BLOCK


    The architecture block defines how the entity operates. This may be described in many ways, two of which are most prevalent: STRUCTURE and DATA FLOW or BEHAVIOR formats. The BEHAVIOR approach describes the actual logic behavior of the circuit. This is generally in the form of a Boolean expression or process. The STRUCTURE approach defines how the entity is structured - what logic devices make up the circuit or design. The general syntax for the architecture block is:

    architecture arch_name of entity_name is
    declarations;
    begin
    statements defining operation;
    end arch_name;

    For our first example, we will use the set/reset NOR latch of figure 1. In VHDL code listings, -- (double dash) indicates a comment line used for documentation and ignored by the compiler.


    library ieee;
    use ieee.std_logic_1164.all;
    --
    -- entity block
    --
    entity latch is
    --
    -- interface signal declarations
    --
        port (s,r : in std_logic;
              q,nq : out std_logic);
    end latch;
    --
    -- architecture block
    --
    architecture flipflop of latch is
    begin
    --
    -- assignment statements
    --
        q <= r nor nq;
        nq <= s nor q;
    end flipflop;

    The first two lines imports the IEEE standard logic library std_logic_1164 which contains predefined logic functions and data types such as std_logic and std_logic_vector. The use statement determines which portions of a library file to use. In this example we are selecting all of the items in the 1164 library. The next block is the entity block which declares the latch's interface inputs, r and s and outputs q and nq. This is followed by the architecture block which begins by identifying itself with the name flipflop as a description of entity latch.

    Within the architecture block's body (designated by the begin reserved word) are two assignment statements. Signal assignment statements follow the general syntax of:

    signal_identifier_name <= expression; The <= symbol is the assignment operator for assigning a value to a signal. This differs from the := assignment operator used to assign an initial literal value to generic identifier used earlier.

    In our latch example, the state of the signal q is assigned the logic result of the nor function using input signals r and nq. The nor operator is defined in the IEEE std_logic_1164 library as a standard VHDL function to perform the nor logic operation. Through the use of Boolean expressions, the operation of the NOR latch's behavior is described and translated by a VHDL compiler into the hardware function appearing in figure 1.

    Time Delays


    Dataflow or behavioral architectures can also include timing influences by adding propagation delay values using the after operator and a time physical data type. An example of adding a one nanosecond inertial delay to our nor gate operation appears as:

    q <= r nor nq after 1ns;


    This would cause output q to go high 1 ns after the application of the r input. The inertial form requires that r stay active, as hold time, for a minimum time equal to the delay time (1 ns in this example). If you want to allow q to change for r pulses that are shorter than 1 ns, you need to use the transport delay form which requires the transport operator to be included preceding the logic statement:

    q <= transport r nor nq after 1ns;


    Again, the difference between the two time delays is that without the transport operator, the input has to be present for the entire delay time while with the transport operator, the input does not need to be applied for a period equal to the full delay time period.

    Using Vector Types in the Architectural Block


    A bit_vector or std_logic_vector type is an array of bits. The range designates the size of the array and the index values to be used by the array. Elements of the array are accessed by using the array name and an index value in the form of: array_name(index). The best way to see how values are assigned to an array is to do an example. This is a multiplexor entity:

    library ieee;
    use ieee.std_logic_1164.all;
    entity demux is
         port ( e : in std_logic_vector (3 downto 0);
                s : in std_logic_vector (1 downto 0);
                d : out std_logic_vector (3 downto 0));
    end demux;
    architecture rtl of demux is
         signal t : std_logic_vector (3 downto 0);
    begin
         t(3) <= s(1) and s(0);
         t(2) <= s(1) and not s(0);
         t(1) <= not s(1) and s(0);
         t(0) <= not s(1) and not s(0);
         d <= e and t;
    end rtl;

    Before we look at this example line by line, we need an introduction to a new declaration, SIGNAL. This declaration is used to define an internal signal for our design. In the entity block we defined interfacing or external signals that take information in and return data out. Internal signals are those used to perform some internal connections or function between logic entities. A signal declaration has the syntax:

    signal signal_identifier : type;

    It is similar to a port signal declaration except for the lack of a mode indication.

    In the demux example, the entity has three array declarations, two are 4-bits (e and d) and one is 2-bits (s). Within the architecture block, a local signal is declared as a 4-bit array (t). The values for t are assigned in descending order directed by the four state combinations of s(1) and s(0). Notice how each element is accessed using the array name and an index value. t(3) is assigned the results of anding s(1) and s(0). This is a single bit manipulation and assignment of one bit from each array, bits t(3), s(1), and s(0). The last line shows how array values can be assigned for the entire array at one time. The crucial requirement is that all arrays in the assignment statement have the same size. If that is the case, than each element of each array is acted upon individually. ie:

    d(3) <= e(3) and t(3)
    etc.

    Since vectors can be assigned using to as well as downto, care must be taken in the assignment. If, in the previous example, d was declared as d : out std_logic_vector( 0 to 3); than the assignment d <= e and t; would assign to d(0) the result of e(3) and t(3) which may not be what you intended.

    Return to Index.

  5. PROCESS


    Statements within architecture blocks, to this point, are executed concurrently - that is at the same time. Also, there is no way to synchronize their execution with clocking or any other kind of signals. To incorporate sequential statement execution and some manner of synchronization, we need to use a PROCESS block whose general syntax form is:

    process_name : process (sensitivity list)
    variable variable_names : variable_type;
    begin
    statements;
    end process;

    Process statements are placed in the architecture block of your design. The process_name and variable declarations are optional. Process names are handy if your design contains more than one process. Variable declarations are used to define a variable local to and used by the process. Variable declarations are added in the declaration area preceding the body of the process block. In contrast to a signal, variable declarations define memory locations, identified by variable identifier names, used to store results of expressions. Signals, by their nature, cannot be used to perform arithmetic manipulations such as incrementing or decrementing their value while variables can be operated on mathematically. The variable assignment operator is := which is the same one used for assigning initial literal values. The syntax for a variable assignment is:

    variable_identifier := expression;

    To evaluate expressions used in a variable declaration or process block, you must become familiar with the operators used by VHDL. Many of them are not strangers to anyone who has any kind of programming experience. In order of their precedence, they are:


    Now an example of a variable assignment:

    cnt := cnt + 1;

    As with any other language, the expression on the right is evaluated first. In this case one is added to the variable cnt. The results are than stored back into the cnt variable indicated on the left side of the assignment statement. This one simply increments cnt by 1. To set this variable statement into a process block, the code would look like:

    count : process(x)
         variable cnt : integer := -1;
         begin
           cnt := cnt + 1;
    end process;

    The first line of the process syntax is its declaration and contains an optional parameter list, known as the sensitivity list. A process executes once at the beginning of a simulation and any time that an event occurs on an item in the sensitivity list. An EVENT is any change of state of a signal. A change of state on signal x will cause this process to execute once.

    The next line is a variable declaration that is similar to a port (signal) declaration. Since it is a variable and not a port, there is no mode selection. Also, variables can be assigned an initial value using an assignment operator as shown in the example. We want cnt to start at 0, but since the process executes once upon starting simulation (without an event occurring on x), we need to initialize cnt to -1. The initial execution of the process due to the start of a simulation will set cnt to 0 by incrementing it once. After that, each time an event occurs on x, cnt will be incremented once, thus keeping track of how many times x changes state. The statements to be executed by the process body follow the begin reserved word. Finally, the process declaration is completed using an end process statement.

    Declarations within the process block and preceding the process body are executed only once - when simulation is initiated. Thereafter, when the process is run due to an event on one of the signals on the sensitivity list, only the body of the process is executed. This prevents variables from being re-initialized each time the process is run.

    All statements in a process execute sequentially. Here are a couple of examples of process statements with an analysis of each:

    process ( Y )
    variable X, Z : std_logic;
    begin
    X := Y;
    Z := not X;
    end process;

    This is a fairly easy appearing example, but let's take some time exploring what happens to make sure you fully grasp the difference between concurrent and sequential operation. Y is included in the sensitivity list, so it must have been declared in the design before the process statement. Variables X and Z are declared in the process block forcing these variables to be local to the process and not accessible outside of it.

    To follow what happens when the process is executed, let's assume some initial values for our three variables:


    Initial values for variables can be set in the variable declaration statements using the := assignment operator in this manner:

    variable X : std_logic := 1;
    variable Z : std_logic := 0;

    Of course, signal Y would have to be initialized before the process statement to give it a beginning state. In this case, you would probably use an assignment statement:

    Y <= '1';

    Since Y has been defined as an interface signal in an entity, the <= assignment operator is required here. Assigning a literal logic state, 1 or 0, to a signal requires a single quote around the 1 or 0. This causes the software to convert the ASCII 1 or 0 to a logic state and assign it to the signal. Assigning a string of logic bit literals to a vector requires double quotes so that the ASCII string can be converted to logic states for each bit of the vector. Numerical literals will not use the quotes around it.

    The sample states were not selected as randomly as you might think. I chose them to illustrate the point of sequential operation within the process. When Y changes to a 0 through some outside influence, an event occurs and the process is initiated. If the statements within the process were executed concurrently, they would use the initial values to produce results for all outputs. The change in Y from 1 to 0 causes X to change to a 0 because of the statement X := Y; Because X had a value of 1 initially, this value is used for the second statement in concurrent execution. This forces Z to become 1 from the statement Z := not X;.

    However, the statements in the process are executed sequentially rather than concurrently. What actually occurs in the process is X becomes 0 when Y changes to 0 as it did for a concurrent execution. However, this time, Z would become 1 since the second statement in a sequential execution would use the new value of X instead of X's initial value.

    Now to a more practical example use of a process which will also include a method to prevent statements within the process body from executing when simulation is first begun and an event has not yet occurred:

    library ieee;
    use ieee.std_logic_1164.all;
    entity DFF is
    -- Signals are initialized to 0 by default. 
    -- To make QN a 1, it has to be initialized
    port ( D, CLK : in std_logic;
             Q : out std_logic;
             QN : out std_logic := '1');
    end DFF;
    architecture data_flip of DFF is
    begin
         process ( CLK )
         begin
            if (CLK = '1' and CLK'event ) then
                Q <= D after 10ns;
                QN <= not D after 10ns;
            end if;
         end process;
    end data_flip;

    There is a lot going on in this short design, so let's examine it carefully. The only wrinkle in the entity block is covered by the comment lines which are always preceded by a double dash (--). Identifiers of all kinds are usually initialized by most compilers when they are declared, to 0 or null. To set QN to the opposite state of Q initially, we had to assign it an initially value of 1 by using := '1' following its port declaration. The rest of the entity block is straight forward.

    In the architecture block we did not require any local variables or signals so none are declared. The process block contains one signal in the sensitivity list, CLK. The only statement in the process body is an if..then..else statement. The if..then..else statement which is explored in more detail later, has a standard format of:

    if condition then
    statements;
    else
    statements;
    end if;

    The else block is optional and is used when there are statements to be executed when the conditional test returns a false result. The then statements are executed when the condition rings true.

    The if..then..else statement in the example has two conditions and both have to be met to execute the statements within the then block. The first condition requires the state of CLK to be high. The and operator in the condition field forces a second condition to also be true. This condition is CLK'event which says that an event must have occurred on CLK to be true. What this format really accesses is a property of the process object called an event. If the event occurred, CLK'event returns true. If no event occurred, it returns a false value. The inclusion of this condition eliminates the execution of the statements within the if block when simulation first begins since the lack of a CLK event causes CLK'event to be false. The only time the if condition will be satisfied is when an event on CLK occurred. Additionally, CLK has to be high, so this combination causes the then statements to be executed only on a positive transition (edge) of the CLK signal.

    By now, you should notice some significant difference in declaring and initializing integers and signals. The := operator is used to assign initial values in a variable statement. Notice that for signals, single quotes are required around the initial value ('0') while none are used for an integer (0). This is because signal values are logic states and integer values are numerical. Numerical values do not require quotes.

    Also notice the difference when integer variables are assigned a value from an expression compared to a signal assignment. In a previous example we used Y <= A and B; to assign to Y the results of A and B. In this most recent example, we did a arithmetic operation on an integer value and assigned the results to it: cnt := cnt + 1; It is very easy to use the incorrect assignment symbol (:= or <=) since they look so similar, so take care!

    Return to Index.

  6. CONDITIONAL STATEMENTS


    if..then..else


    The primary conditional test function is the if..then..else construct that works the same as it does in any programming language. The syntax for this function is:

    if conditional_test then
    statements;
    else
    statements;
    end if;

    The statements following then are executed if the condition is true. The else block is optional and used only if there is an alternate process required to be done if the conditional result is false. If statements can be nested using an elsif block. In that case, the syntax is:

    if conditional test then
    statements;
    elsif conditional test then
    statements;
    else
    statements;
    end if;

    If a compound conditional is desired then parenthesis may be used to encase the entire conditional test section. Here is an example if..then..else application:

    count : process(x)
          variable cnt : integer := 0;
    begin
          if (x = '1' and x'last_value = '0') then
              cnt := cnt + 1;
          else
              cnt := cnt - 1;
    end if;

    This code section is processed whenever an event occurs on x. The if statement checks to see if x is now 1 and was previously a 0 (x'last_value = '0'). If this is true cnt is incremented once. If this is false, which means that x changed from a 1 to a 0, then cnt is decremented once. The final result will tell the user what the last change was (a cnt value of 1 indicates a change from 0 to 1 and a 0 cnt value indicates the last change was from 1 to 0).

    Other Conditional Statements


    1. if..then..else Short Form, the WHEN statement:


      Syntax


      identifier <= expression_true
      WHEN condition
      ELSE expression_false;


      Example:

      Y <= A and B WHEN S = '0'
      ELSE A or B;

    2. Full WHEN statement


      Syntax


      identifier <= expression1
      WHEN condition 1
      ELSE
      expression2 WHEN condition2
      ELSE
      expression3 WHEN condition3
      ELSE
      expressionN WHEN OTHERS;

      Identifier is assigned the expression for the WHEN condition that is true.


      Example:

      print1 <=
      user1 WHEN (en = '1' and sel = '0') ELSE
      user2 WHEN (en = '1' and sel = '1') ELSE
      user3 WHEN OTHERS;

    3. CASE Statement


      Syntax

      FONT color="#0000FF">CASE test_var IS
      WHEN test_val1 => identifier <= expression1;
      WHEN test_val2 => identifier <= expression2;
      WHEN test_val3 => identifier <= expression3;
      WHEN test_val4 => identifier <= expression4;
      WHEN test_val5 => identifier <= expression5;
      WHEN OTHERS => identifier <= expression6;

      := may be used instead of <=

      Conditional test is done on all values concurrently. Assignment is made for true WHEN condition.


      Example:

      TYPE op IS (ADD, SUB, MUL, DIV);
      SIGNAL op_code : op;
      PROCESS (op_code, A, B)
      BEGIN
      CASE op_code IS
      WHEN ADD => Y := A + B;
      WHEN SUB => Y := A - B;
      WHEN MUL = > Y := A * B;
      WHEN DIV => Y := A * B;
      WHEN OTHERS => Y := Y;
      END CASE;
      END PROCESS;


    4. WITH Statement


      Syntax


      WITH test_variable SELECT
      identifier <=
      expression1 WHEN test_val1,
      expression2 WHEN test_val2,
      expression3 WHEN test_val3,
      expression4 WHEN OTHERS;


      Example


      WITH SEL SELECT
      Y <=
      A WHEN "00",
      B WHEN "01",
      C WHEN "10",
      D WHEN "11",
      'Z" WHEN OTHERS;


    Return to Index.

  7. LOOPS


    The For Loop


    The for loop is used to repeat the execution of a section of code for a given number of times. The general syntax for a for loop is:

    for variable in range loop
    statements;
    end loop;

    The range has the same format as the range used in a bit_vector assignment except, in a for statement, it also defines the direction for the value of the variable for each iteration of the loop (to increments and downto decrements). Here is an example:

    signal x : bit_vector (7 downto 0);
         .
         .
    process(x)
        variable p : std_logic;
    begin
        p := '0';
        for i in 7 downto 0 loop
           p := p xor x(i);
        end loop;
    end process;

    The first line is a signal assignment not enclosed in an entity. A local variable (p) is assigned within the process and it is initialized to 0 when the process is begun in response to an event on x. The eight bits of x are exclusively ORed with each other to produce the even parity for the word in x. When entering the loop, i is initialized to the first value in the range (7). In the first iteration of the loop, x(7) is XORed with p (0), the result is returned to p, and i is decremented. In the second iteration, x(6) is XORed with p again and the result, once more, is returned to p and i is decremented again. This sequence repeats until i = 0. The last iteration, XORs x(0) with the accumulated result in p and exits the loop. When the loop is finished, p will hold the even parity state for word x.

    WHILE LOOPS

    Return to Index.

  8. STRUCTURE DESIGN


    Recall that the architecture block defines how the entity operates and can be described in many ways, two of which are most prevalent: structural and behavioral (data flow). We have already looked at the behavior type of architecture. This section explores the structural approach which defines how the entity is constructed - what logic devices make up the design. The general format for the structural architecture is:

    architecture architecture_name of entity_name is
    internal interconnecting signal declarations;
    component (object) declarations;
    begin
    iterations (copies) of components;
    end architecture_name;

    Return to Index.

  9. COMPONENTS


    A component is created from any previously designed entity. It can be declared on the same page as the current design or imported from a library. A component defines the design (object) which can be instantiated or copied. As such, there are some definite procedures to follow when using components. The general syntax for a component declaration is:

    component entity_Name
    port (names: mode type);
    end component;

    Components are related to the entity through the use of the same name and port list. Port signal declarations must be in the same order as the entity port declarations and must have the same name, mode and type - after all, a component is going to be nothing more than a reference for a copy of an entity design.

    The content of a component block must match the content of the source entity block exactly. Once the component is declared, it may be instantiated (copied) and reused any number of times within the design by the use of this code:

    part_name : component_name
    port map (signals in exact order);

    For an example, we will use the following NOR latch which is similar to one in figure 1:


    And here is the entity and architecture blocks of the structure design:

    library IEEE;
    use IEEE.std_logic_1164.all;
    
    -- NOR gate entity design
    
    entity nor_gate is
          port (a,b : in std_logic;
                   c : out std_logic);
    end nor_gate;
    architecture my_nor of nor_gate is
    begin
         c <= a nor b;
    end my_nor;
    
    -- begin latch design
    
    entity latch is
         port (s,r : in std_logic;
               q,nq : out std_logic);
    end latch;
    architecture flipflop of latch is
    
    -- NOR gate component declaration
    
         component nor_gate
             port (a,b : in std_logic; c out std_logic);
         end component;
    begin
    
    -- instantiation of two NOR gates
    
         n1 : nor_gate
            port map (r, nq, q);
         n2 : nor_gate
            port map (s, q, nq);
    end flipflop;

    A number of concepts are illustrated by this example, so let's explore each of them. First, we have the component declaration placed in the architecture block preceding the architecture body. It starts with the reserve word component followed by the component's name, in this case - nor_gate. As with an entity, signals for the component are declared using the port function. In this example we have two inputs, a and b and one output, c. An end component statement completes the declaration. This creates a component OBJECT. Notice that the nor_gate component and its corresponding entity declarations are identical except for the component and entity reserved words. The importance of this, is that we will declare two instances of this object, each of which will inherit the properties of the nor_gate component object.

    The first instance is n1 and notice that its declaration is placed in the body of the architecture block. This means that the architecture wants to use this instance for the latch. In order to inherit nor_gate properties, the signals used by the instance must be MAPPED from the nor_gate object. This is accomplished using the port map statement. The signals mapped must be in the same order as the component object. In this case r and nq inherit the input function from a and b of the nor_gate object. q gets the output function from c in the nor_gate component.

    In this simple example, there are no extra interconnecting signals. All connections are established using signals of the NOR gate components. Here is a little different design that incorporates interconnecting signals between components.

    Return to Lesson Index.

  10. SIGNALS


    mux

    Before we code this one, let's take a close look at the circuit. There are four parts: two AND, one OR, and one INVERTER gate. Many of the component signals are connected to external signals:


    There are also internal signal connections between components which have the following labels:


    In a structural design, the internal signals are declared using the SIGNAL declaration which has the syntax:

    SIGNAL : TYPE;

    Notice the lack of a mode designation in the signal declaration. This is because, these are interconnecting signals between components. This means that they are connecting an output from one component to the input of another. That alone would make it difficult to declare a mode for them. Secondly, they are internal interconnecting signals. Direction is determined by existing port interface declarations of the entities of the components being connected.

    For a structural design, you are only stating how parts are connected together. The design depends on previously defined behavior of the individual parts which determine how the design will work. With all of this, let's code the design and then take a good look at it.

    library IEEE;
    use IEEE.std_logic_1164.all;
    
    -- Define three logic components
    -- AND Gate
    
    entity my_and is
         port ( A, B : in std_logic;
          Y : out std_logic );
    end my_and;
    architecture and_gate of my_and is
    begin
         Y <= A and B;
    end and_gate;
    
    -- OR gate
    
    entity my_or is
         port ( A, B : in std_logic;
          Y : out std_logic );
    end my_or;
    architecture or_gate of my_or is
    begin
         Y <= A or B;
    end or_gate;
    
    -- Inverter gate
    
    entity my_inv is
         port ( A : in std_logic;
           Y : out std_logic );
    end my_inv;
    architecture inv_gate of my_inv is
    begin
         Y <= not A;
    end inv_gate;
    
    -- Start 2-bit multiplexor
    
    entity my_mux is
           port ( Sel, Ain, Bin : in std_logic;
            Dout : std_logic );
    end my_mux;
    architecture mux_ckt of my_mux is
    
    -- signal declarations
    
    signal S0, S1, NotS : std_logic;
    
    -- component declarations
    
    component my_and is
        port ( A, B : in std_logic;
          Y : out std_logic );
    end component;
    
    component my_or is
        port ( A, B : in std_logic;
          Y : out std_logic );
    end component;
    
    component my_inv is
        port ( A : in std_logic;
          Y : out std_logic );
    end component;
    
    -- structural design body
    
    begin
       X1 : my_and port map ( Sel, Ain, S1 );
       X2 : my_and port map ( Bin, NotS, S0 );
       X3 : my_inv port map ( Sel, NotS );
       X4 : my_or port map ( S1, S0, Dout );
    end mux_ckt;

    From the top! This design begins by defining three logic gates, my_and, my_or, and my_inv for AND, OR, and INVERTER gates. Keep in mind, that all three of these gates could also have been imported from a library file. More on libraries in a later section. For now, this design includes the three entity designs for the logic gates. Each architecture of the gate designs is in behavior or data flow form. That is the behavior of the gate is described for each. In the architecture block there are several declarations made before the beginning of the body portion. The first declares the internal signals used in the design. Since VHDL is hierarchical in nature, these signal declarations have to made before the component instantiations that use them. Again, notice the lack of mode statements in the signal declarations.

    The remaining declarations define the components used by the architecture. Here you will see copies of the entity declarations, but instead of the reserved word entity, you use component. After their declarations starts the body of the architecture block designated by the begin reserve word. Look at the simplicity of this part of the design. Its only a list of components and how signals are connected to them. X1 and X2 are copies of my_and, X3 of my_inv, and X4 of my_or. Notice that the port map signals are in the same order as the component and entity port declarations. Suppose you do not know the order. There is a way around that known as DIRECT ASSOCIATION which does require, minimally, that you know the port signal names and their intended use. Here is an alternate way of instantiating X1 as my_and:

    X1 : my_and
           port map (
    	   A => Sel, 
            Y => S1,
            B => Ain );

    The order of the port map list no longer matters since each individual signal relationship is defined individually. In effect, instantiating components is similar to placing logic ICs, like the 7400 series, onto a protoboard (components) and interconnecting them with wires (interconnecting signals). Logic signals are applied to inputs and outputs are monitored (interface - entity port signals).

    Return to Lesson Index.

  11. MODULAR DESIGN


    Now that my_mux is completed, it can be used as a component or MODULE in a larger design, say an 8-bit multiplexor. You must start by treating the my_mux design as a component with three inputs (Ain, Bin, Sel, and Dout) that could be represented by this block:

    mux2

    Basically, just continue with the mux design by adding a new entity:

    entity eight_mux is
        port ( Ain, Bin : in std_logic_vector (7 downto 0 );
           Dout : out std_logic_vector ( 7 downto 0 );
           Sel : in std_logic );
    end eight_mux;
    architecture multiplex of eight_mux is
    
    -- declare my_mux component
    
    component my_mux is port map ( Ain, Bin, Sel : in std_logic; 
    Dout : out std_logic );
    end component;
    
    -- instantiate my_mux component eight times
    
    begin
       M7 : my_mux port map ( Ain(7), Bin(7), Sel, Dout(7) );
       M6 : my_mux port map ( Ain(6), Bin(6), Sel, Dout(6) );
       M5 : my_mux port map ( Ain(5), Bin(5), Sel, Dout(5) );
       M4 : my_mux port map ( Ain(4), Bin(4), Sel, Dout(4) );
       M3 : my_mux port map ( Ain(3), Bin(3), Sel, Dout(3) );
       M2 : my_mux port map ( Ain(2), Bin(2), Sel, Dout(2) );
       M1 : my_mux port map ( Ain(1), Bin(1), Sel, Dout(1) );
       M0 : my_mux port map ( Ain(0), Bin(0), Sel, Dout(0) );
    end eight_mux;

    That's it. Notice that the eight bit multiplexor is easier to design because most of the preliminary work was already done. You are merely making use of previously defined components at this point. This is the power of structural design.

    Return to Lesson Index.

  12. FUNCTIONS


    A function in VHDL is similar to functions in most upper level languages. It is a subprogram that accepts input parameters and returns a single result. The syntax for a function declaration is:

    function function_name ( formal parameters )
        return return_type is
    	variable declarations;
    	begin
    	   statements;
    	return return_variable_name;
    end function_name;

    Here is an example of a function that computes the parity on a data array of unspecified length:

    function parity ( word : std_logic_vector )
         return std_logic  is
         variable tmp : std_logic;
         begin  
         for  i  in  word'range  loop
             tmp : = tmp xor word(i);
         end loop;
         return  tmp;
    end parity;

    When the function is called, a specific variable of std_logic_vector type is passed to it. A tmp variable of std_logic type is declared and, by default, is initialized to zero. The for loop exclusive ORs tmp with each bit of the word array. The loop repeats from 1 to the number of bits in the array (word'length). The final value of tmp (the even parity state of the word passed in) is passed out of the function via the return statement.

    Function Call


    Functions are called using a single assignment statement that has the general form of:

    variable <= function_name ( actual parameters );

    The actual parameter(s) passed to the function must be of the same type and length (if a specified array size was made) as the formal parameter(s) in the function declaration. The returned value of the function is stored into the variable on the left side of the expression.

    Function call example:

    even_parity <= parity( data_bus_in );

    This function call accepts the vector array data_bus_in and calculates its parity, returning the even parity result to the variable even_parity.

    Return to Lesson Index.

  13. PROCEDURES


    A procedure like a function is a subprogram that must first be declared and then called. Unlike the function, procedures can pass out numerous results through its parameter list. Because of this, parameter declarations must include an in or out mode declaration as well as a data type indication. The syntax for declaring a procedure is:

    procedure procedure_name (formal parameter : mode type,
    formal parameter : mode type ) is
    variable declaration;
    begin
    statements;
    end procedure_name;

    Since there are possible multiple results, procedures are not called using an assignment statement like a function. Instead they are called using this format:

    procedure_name (actual parameter list);

    The parameter list contains the names of the actual parameters to be passed in and out of the procedure and while they do not have to have the same identifier names as those in the declaration, they must follow the exact order as well as having the same mode and type as the formal parameters in the procedure declaration. The following procedure returns two results Z and ZCOMP based on the values of words A and B and the type of operation to be performed on them.

    type op_code is ( ADD, SUB, MUL, DIV, LT, LE, EQ );
    .
    .
    procedure ARITH_UNIT ( A, B : in integer; op : in op_code; Z 
    : out integer; ZCOMP : out boolean ) is
    begin
        case op is
    	when ADD => Z := A + B;
    	when SUB => Z := A - B;
    	when MUL => Z := A * B;
    	when DIV => Z := A / B;
    	when LT => ZCOMP := A < B;
    	when LE => ZCOMP := A <= B;
    	when EQ => ZCOMP := A = B;
    	when others => Z := Z;
        end case;
    end ARITH_UNIT;

    Somewhere, preceding the procedure declaration is an enumerated user type declaration creating a new type op_code with the values indicated in the parenthesis. The procedure ARITH_UNIT is declared with three input parameters passed into the procedure - A and B which are integer types and op which is an op_code type. Two output parameters, integer Z and Boolean ZCOMP contain the results passed out by the procedure. In the body of the procedure, op is checked for a value using a case statement and dependent on that result, one of the when assignments is performed. An example statement that calls this procedure is:

    ARITH_UNIT ( word1, word2, operation, result, comp_check );

    word1 and word2 are passed in as parameters A and B. operation enters as parameter op. The results of the procedure are passed out through parameters Z and ZCOMP to variables result and comp_check.

    Return to Lesson Index.

  14. State Diagram Entry


    In this section, we will create a mod-3 counter using the ALDEC state diagram entry tool.

    1. Open Active HDL and select an existing design or start a new design
    2. In the design browser window, right click on new design and select NEW - STATE DIAGRAM
    3. In the NEW SOURCE FILE WIZARD window, select ADD THE GENERATED FILE TO THE DESIGN and NEXT
    4. In the NAME OF SOURCE FILE box, type in CNTR and click NEXT
    5. The DESIGN WIZARD - PORTS window shows next. Enter the port names in the following sequence:
      1. Click NEW. Type in reset and select IN if not already selected.
      2. Click NEW. Type in enable and select IN if not already selected.
      3. Click NEW. Type in clk and select IN if not already selected.
      4. Click NEW. Type in tc and select OUT if not already selected.
      5. Click NEW. Type in q and select OUT if not already selected. Click TYPE to get PORT TYPE window. Select STD_LOGIC_VECTOR from the pull down menu. Set the range to 1 and 0. Check registered and click OK.
      6. Click FINISH to exit Design Wizard - Ports.
    6. In the work area you will now have the state diagram page. Click on the magnify icon in the tool bar twice to enlarge the page. Scroll so that the top of the page is visible. At the top, the interface signals are indicated as input and output terminals. Note the transition indication in the q output terminal. This indicates that these outputs are registered outputs.
    7. Right click on the clk terminal and select PROPERTIES.
    8. In the PORT PROPERTIES window, check the square box next to CLOCK. Click APPLY and OK. In the clk terminal you will now see a square wave indicating the clocking function.
    9. Scroll down so that you get as much white space in the work area as possible.
    10. Click on the STATE bubble icon on the tool bar (a circle with an S1 in it. Drag the circle to the center top of the work area. Leave some room at the top.
    11. Repeat this process twice more for S2 and S3. Place these state bubbles under the S1, one to the left and one to the right. Leave some space for branches and labels.
    12. Click the BRANCH icon ( slightly bent arrow moving from lower left to upper right). A "plus" cursor appears. Click on the edge of the S1 state bubble to anchor the branch and drag the branch line to the edge of S2 and click to anchor the other end. You need to make sure that you are on the edges of the state bubbles each time you anchor the branch.
    13. Repeat the process to connect a branch from S2 to S3 and S3 to S1. Make sure you retain the same order as indicated: S1 to S2, S2 to S3 and S3 to S1 as shown in figure 1.

      figure 1

    14. If you make a mistake, you can remove an item by selecting (clicking) on it and pressing the DELETE key.
    15. Click on the BRANCH icon once again. This time anchor a branch between two edge spots on the S1 bubble. This will create the "no change" branch which originates and ends on the same state bubble.
    16. Repeat this process for the S2 and S3 states. Your diagram should now l;ook like figure 2.

      Figure 2

    17. Right click on the branch between S1 and S2. Select PROPERTIES and the HDL tab in the TRANSITION PROPERTIES window.
    18. Under CONDITION, you are going to write the VHDL code for an if..then condition. Type in:
      reset = '1' and
      enable = '1'
    19. Under ACTION you are going to type in the result of makling the transition from S1 to S2. Type:
      tc <= '0';
    20. Click APPLY.
    21. Repeat this process for the branches from S2 to S3 and S3 to S1. The only thing different is that in the transition from S2 to S3 the action is tc <= '1'; instead of tc <= '0';.
    22. You can move the label boxes as needed to make the diagram neater by selecting the label text box and drag it to a new position..
    23. Repeat the process for the "no change" branches. Type this text for all three:
      conditions: reset = '1' and enable = '0'
      action: tc <= '0';
    24. Click OK when finished with all transition labling to close the transition properties window.
    25. Right click on the green area inside the S1 bubble. Select PROPERTIES and ACTION tab.
    26. In the state box, type: q <= "00"; and click APPLY.
    27. In the S1 state bubble, click on S1 to select it. A small box appears around S1 to show it has been selected. Drag it to the top of the bubble. Click on the q text box outside of the bubble and drag it into the bubble underneath S1.
    28. Repeat the process for S2 and S3 using q <= "01"; for S2 and q <= "10"; for S3.
    29. When finished click OK to close the action properties box.
    30. On the tool bar, select the RESET icon (a triangle with a zig zag arrow pointing down) and drag the triangle near the S1 state bubble.
    31. When you click on the triangle to set it in place, a plus cursor appears. Drag the branch line to the edge of the S1 vbubble and click on it to anchor the branch there.
    32. Right click on the triangle and check the ASYNCHRONOUS selection. Click OK.
    33. Right click on the branch from the reset triangle to S1. Select PROPERTIES and the HDL tab.
    34. Under conditions type: reset = '0' and under action type tc <= '0';. Click APPLY and OK.
    35. Move labels for neatness.
    36. Click on any white space to deselect all items. You should have the completed state diagram of figure 3.

      Figure 3

    37. Compile your design.
    38. In the design browser window, click the + next to CNTR.asf. You will see the CNTR.vhd file. Double click on it to view it in the work area.





    39. YOU CANNOT EDIT THIS FILE. All editing will have to be done in the CNTR.asf file. If you you wanted to, you could copy and paste this listing into a separate file and edit it there.
    40. In the design browser, right click on cntr(cntr_arch) and set it as top level.
    41. Click on the waveform icon in the tool bar to get the waveform pull down menu.
    42. From the waveform pull down menu, select ADD SIGNALS.
    43. In the add signals box, right click on any signal and select SELECT ALL. Click on ADD and then CLOSE.
    44. Right click on any signal in the work window. Select STIMULATORS. Stimulate the inputs as follows:
      1. 250 MHz clock for clk
      2. 0 0, 1 05 ns for reset
      3. 0 0, 1 10 ns for enable
    45. Set run time for 50 ns and click on the underlined arrow head icon next to the run time box to run the simulation. Expand the waveform using the maginiffy icon. You will get the waveform results of figure 4.

      Figure 4

    46. The waveform shows the changes in state and q output as well as the state of tc.
    47. Close the design and Active HDL.

    Return to Lesson Index.

  15. LIBRARY FILES


  16. Libraries are a convenient way to store and retrieve designs, functions, procedures, and other commonly used items. You should be familiar with the IEEE STD_LOGIC_1164 LIBRARY by now if you have created a simple design. This library contains definitions for many of the VHDL IEEE standard data types and logic functions. However, you do not need to use this library to create a logic design. VHDL has a built in library that is automatically accessed without a library or use statement. This library is specified by IEEE as VHDL 1076 and contains some of the basic definitions of VHDL reserve words and operators. For instance, here is a design for an AND gate that uses only the VHDL 1076 built in library:

    entity and_gate is
    port ( A : in bit_vector ( 1 downto 0 );
    Y : out bit);
    end and_gate;
    architecture and of and_gate is
    begin
    Y <= '1' when A = "11" else '0';
    end and;

    This is a complete design that does not require any additional libraries. It defines an AND function using VHDL 1076 terms only.

    To access an existing library, you must declare the location of the library and which parts of it you want to use. The syntax for access the entire contents of the IEEE.STD_LOGIC_1164 is:

    LIBRARY IEEE;
    USE IEEE.STD_LOGIC_1164.ALL;

    The library statement denotes the parent library of files which contains defined functions, procedures, operators, and processes. This declaration must precede your design. The use statement selects which groups of files from the parent library you want to use in your design. The most common use call is all to indicate you want access to the entire contents of the library file.

    Each entity in your design must be preceded by the use clause if that section of the design file is to use the contents of the 1164 library. When you compile your design, the requested files from the library are accessed and added to your design. In turn, at the completion of the compile function, your design is added to a working library, aptly referenced to by the reserved word WORK. The contents of this library are accessible by any design in the current open project as long as you indicate it use with the statement:

    use work.package_name.all;

    PACKAGES are units that are defined at the beginning of your design to hold commonly used functions, procedures, constants, etc. We will get into details about packages shortly. Additionally, designs in the current file can be accessed from the library that the compile process stores completed designs into when they are successfully compiled. For instance if you are working on a project you named my_gates, than a library with that name has been created and holds copies of your designs that are in that project. You can access them for new designs by denoting the project name as the parent library in the library and use statements:

    LIBRARY my_gates;
    use my_gates.all;

    If this library is in a different folder from your current design, then you must specify the parent library name in the library and use statements:

    LIBRARY my_designs;
    use my_designs.my_gates.all;

    Many VHDL library files have been created to store commonly used functions and device entities. Among them are three fairly commonly used files, std.textio for handling input and output functions, ieee.arith which contains many mathematical processes, and ieee.std_logic_1164 which is an IEEE standard for logic devices and symbols. Library files are included into a listing by using the reserved word use which defines which parts of the library file are used.

    One use of std.textio is to send data to the screen with write and writeline functions. The textio functions are usually included in a standard library file that is always available to the VHDL interpreter, hence there is no need to declare its parent library in the library and use statements. Instead, the prefix std is used to access the standard library file. A write(variableName,data) appends the data to the variable designated by variableName. Writeline(output,variableName) directs the contents of the variable called variableName to the standard output port which is the monitor screen. Here is an example use of the write statements:

    use std.textio.all;
    .
    .
    write(s,"Counter Overflow - ");
    write(s,cnt);
    writeline(output,s);
    .
    .

    One requirement is to make sure that s is empty before you start. The first write statement appends the text Counter Overflow - to variable s. The second statement takes the value of variable cnt and appends that to s. The third statement sends the message to the screen. If the value of cnt was 33, than the message on the screen would be: Counter overflow - 33.

    PACKAGES

    As briefly mentioned earlier, packages are a way of storing commonly used items in a library file. If the package is to hold basic items like constants and type declarations then it is only required to define the package at the beginning of a design. The package will be added to that design's library when it is compiled. If you wish to create a library strictly for holding packages, then do not include any designs (entity declarations). When it is compiled, the defined packages will be saved in the created library file. The syntax for a simple package declaration is:

    package package_name is
    package_contents;
    end package_name;

    If the package is declared at the beginning of a file containing actual circuit designs, then the designs can access the packages contents using the use statement and work library:

    use work.package_name.all;

    Here is an example design of a four bit rotate operation using a local package:

    library ieee;
    use ieee.std_logic_1164.all;
    --
    -- package declaration
    --
    package my_types is
    --
    --create sub type for an eight bit vector
    --create a constant called clear which is of byte type 
    --initialized to 0
    --
    subtype byte is std_logic_vector (7 downto 0);
    constant clear : byte := (others => '0');
    end my_types;
    --
    --define library uses for rotate design
    --
    use ieee.std_logic_1164.all;
    use work.my_types.all;
    --
    --declare rotate entity
    --
    entity rotate is
    port (clk, reset, load : in std_logic;
    --
    --declare two buses of subtype byte from package my_types
    --
    data in : byte;
    Q : out byte);
    end rotate;
    --
    --architecture of rotate
    --
    architecture rotate4 of rotate is
    --
    --rotate process
    --
    process (clk, reset)
    signal Qtemp : byte;
    begin
    --
    --check for reset and set Qtemp to clear constant
    --
    if reset = '1' then Qtemp <= clear;
    --
    --check for positive edge trigger of clk
    --
    elsif (clk = '1' and clk'event) then
    --
    --check load, set to data input if load = 1
    Qtemp <= data when (load = '1') else
    --
    --do rotate if load = 0
    --
    Qtemp := Qtemp(byte'length-1 downto 1) and Qtemp(0);
    end if;
    Q <= Qtemp;
    end process;
    end rotate4;

    For functions and procedures, they are packaged by first declaring the package and then defining the subprogram in a PACKAGE BODY. Here is an example creation of an eight bit register procedure which is stored into a library package called my_8bit_reg:

    --First, the package declaration
    --
    library ieee;
    use ieee.std_logic_1164.all;
    package my_8bit_reg is
    subtype byte is std_logic_vector (7 downto 0);
    constant clear : byte := (others => '0');
    procedure reg8 (reset, clk : in std_logic; data_in : byte; 
    Qout : byte);
    end my_8bit_reg;
    --
    --create package body containing actual procedure
    --
    package body my_8bit_reg is
    procedure reg8 (reset, clk : in std_logic; data_in : byte; 
    Qout : byte) is
    begin
    if reset = '1' then Qout <= clear;
    elsif clk = '1' and clk'event then
    Qout <= data_in;
    end if;
    end reg8;
    end my_8bit_reg;

    This can be compiled as is, which would store a package called my_8bit_reg into the current library, such as my_gates. It can be accessed by other projects with these preceding statements:

    library my_gates;
    use my_gates.my_8bit_reg.all;

    Return to Index.

  17. MULTIPLE ARCHITECTURE DESIGNS


    Logic designs can have only one entity, but may have multiple architectures. The last architecture in a design is the default architecture. As an example, here is a design for an AND gate with two architectures:

    library ieee;
    use ieee.std_logic_1164.all;
    entity my_and is
      port ( A, B, C : in std_logic;
    	    Y : out std_logic );
    end my_and;
    --
    -- architecture 1
    --
    architecture and2 of my_and is
    begin
    	Y <= A and B;
    end and2;
    --
    -- architecture 2
    --
    architecture and3 of my_and is
    begin
    	Y <= A and B and C;
    end and3;

    There are two ways to access the 3-input AND gate as a component. One is to take advantage of its default status as the last architecture of the my_and design by applying this use statement after the library statement:

    use my_gates.my_and.all;

    The other way is to specify the precise architecture you want to use. This is the only way you can access the the two-input gate:

    use my_gates.my_and.and3; for the three input gate.

    use my_gates.my_and.and2; for the two-input gate.

    You cannot import both architectures into the same design. The component declaration is the same for each choice since it must match the entity declaration exactly. When instantiating the component, copies must indicate a defined signal for each port signal in the entity/component statement even if one is not used (input C in the two-input AND gate) as illustrated in the following example.

    library ieee;
    use ieee.std_logic_1164.all;
    library my_gates;
    use my_gates.my_and.and2;
    entity and4 is
    	port ( W, X, Y, Z : in std_logic;
    		  Q : out std_logic );
    end and4;
    architecture four_and of and4 is
    	signal S, T, U, V, M : std_logic;
    	component my_and is
    		port ( A, B, C : in std_logic;
    			  Y : out std_logic );
    	end component;
    begin
    	gate1 : my_and port map ( X, W, U, S );
    	gate2 : my_and port map ( Y, Z, V, T );
    	gate3 : my_and port map ( S, T, M, Q );
    end four_and;

    In this design, signals U, V, and M are dummy signals used to satisfy the entity and component port declarations in the instantiations for gate1, gate2, and gate3. The final Boolean expression for this circuit is Q = W and X and Y and Z. gate1 uses inputs W and X to produce output S. gate2 has inputs Y and Z and output T. Finally, gate3 uses S and T to generate circuit output Q.

    Return to Index.

  18. Lab Pages


    The set of links below will take you to a number of labs based on VHDL designs. The first three
    are tutorials for the three leading development packages:

    1. Altera's MAX+plus2 development software.
    2. Xilinx's Foundation software.
    3. Aldec's Active HDL design tools.

    These labs were written with Active HDL in mind, but can
    be adopted to any design software:

    1. Creating Components
    2. 8-Bit Adder
    3. Arithmetic Unit
    4. Memory Using CASE and ARRAYs
    5. State Machine Entry

    These next set of labs were designed around Altera's MAX+plusII Software,
    but can also be adopted to any other design software:

    1. Adder/Subtracter
    2. Arithmetic Logic Unit
    3. Registers
    4. State Diagram Entry

      Return to Index.

    5. REFERENCE


      Here a list of VHDL syntax:

      - comment
      library ieee;
      use ieee.std_logic_1164.all;
      entity entityName is
      port (signalName : mode type);
      end entityName;
      signal name : mode type;
      signalName <= expression;
      signalName <= value when (condition)
      else otherValue;
      architecture entityName is
      assignments;
      begin
      statements;
      end entityName;
      processName : process (sensitivity list)
      assignments;
      begin
      statements;
      end processName;
      if (condition) then
      statements;
      else
      statements;
      end if;
      if (condition) then
      statements;
      elsif (condition)
      statements;
      else
      statements;
      end if;
      for loop_variable in range loop
      statements;
      end loop;
      range notations:
      
      value downto value
      value to value
      

      Return to Index.

    6. EXAMPLE FILES


      8-bit Comparator


      -- 8-Bit Comparator
      library ieee;
      use ieee.std_logic_1164.all;
      entity compare is                                         
      port (A,B : in bit_vector(0 to 7);
      	 EQU : out bit );
      end compare;
      architecture compare1 of compare is                
      begin
          EQ <= '1' when (A = B) else '0';                
      end compare1;

      -----------------------------------------------------------

      8-bit shift register


      -- 8-Bit Shift Register
      library ieee;
      use ieee.std_logic_1164.all;
      entity rotate is
          port (CLK, RST, LOAD : in bit;
                Data : in bit_vector(0 to 7);
                Q : out bit_vector(0 to 7));
      end rotate;
      architecture rotate1 of rotate is
      begin
         reg:process(RST, CLK)
            variable Qreg : bit_vector(o to 7);        
         begin
              if(RST = '1') then
                 Qreg := "00000000";                   
              elsif(CLK = '1' and CLK'event) then
                 if(LOAD = '1') then Qreg := Data;
      .          else
                    Qreg := Qreg(1 to 7) & Qreg(0);               
      	      end if;
             end if;
           Q <= Qreg;                                  
         end process;
      end rotate1;

      ------------------------------------------------------------

      Second 8-bit shift register


      -- 8-Bit Shift Register Two
      library ieee;
      use ieee.std_logic_1164.all;
      entity rotate is
          port (CLK, RST, LOAD : in bit;
                Data : in std_logic_vector(0 to 7);
                Q : out std_logic_vector(0 to 7));
      end rotate;
      procedure dff(signal Rst, Clk : in bit;
        signal D : in std_logic_vector(0 to 7);
        signal Q : out std_logic_vector(0 to 7)) is
      begin
        if(Rst = '1') then
          Q <= "00000000";
        elsif(Clk = '1' and Clk'event) then
          Q <= D;
        end if;
      end diff;
      architecture rotate2 of rotate is
        signal D, Qreg : std_logic_vector(0 to 7);
      begin
        D <= Data when(LOAD = '1')
        else
          Qreg(1 to 7) & Qreg(0);
        diff(Rst, Clk, D, Qreg);
        Q <= Qreg;
      end rotate2;

      ----------------------------------------------------------

      CASE Example: 4 to 1 Multiplexer

      library IEEE;
      use IEEE.std_logic_1164.all;
      -- declare mux entity
      entity my_mux is
      port (  A, B, C, D : in std_logic;
                     Sel : in std_logic_vector ( 0 to 1 );
                     Y : out std_logic );
      end my_mux;
      -- architecture block
      architecture mux_4 of my_mux is
          constant mux_delay : time := 15ns;
      begin
          mux_proc : process ( A, B, C, D, Sel )
                  variable Y_temp : std_logic;
          begin
                  case Sel is
                          when "00" => Y_temp := A;
                          when "01" => Y_temp := B;
                          when "10" => Y_temp := C;
                          when "11" => Y_temp := D;
                          when others  => Y_temp := 'X';
                  end case;
                  Y <= Y_temp after mux_delay;
           end process mux_proc;
      end mux_4;


      Return to Index.

    7. VHDL Questions

      1. Introduction


      1. What type of language is VHDL?
      2. What is the basic building unit of a VHDL design?
      3. What do all VHDL designs begin with?
      4. Which block describes a design's interface?
      5. Which block describes a design's behavior?
      6. What is the difference between simulation and synthesis?

    8. Data Types


      1. VHDL is a ____________ typed language.
      2. Which data type defines a single logic signal?
      3. Which data type describes a bus?
      4. What two ways can a vector's range be described?
      5. What are the IEEE STD_LOGIC_1164 data types for single logic signals and buses?
      6. Why is it desirable to use IEEE STD_LOGIC_1164 data typing?
      7. What are the only two values for a Boolean type?
      8. What are the numerical data types?
      9. What is SUBTYPING used for?
      10. What type is use to create a user data type?
      11. What reserved word is used to declare a user data type?
      12. Create the use data type DAYS and assign it the values: MON, TUE, WED, THU, FRI, SAT and SUN.
      13. Which data type is used for a string of ASCII characters?
      14. Which data type includes time units as values?

    9. Entity


      1. Create the entity block for a three input XOR gate.
      2. Which symbol is used to end all VHDL statements?
      3. What part of a port declaration defines a signals in or out direction?
      4. Which VHDL construct is used to define a literal constant in an entity block?
      5. Create the integer constant included in an entity block, called BUS_SIZE and assign it a value of 32.
      6. Which symbols are used as an assignment operator to assign a literal to an identifier name?

    10. Architecture Block


      1. What are the two primary ways to describe a logic circuits function within an architecture block?
      2. Create the architecture block for the 3-input XOR gate of question 21.
      3. Which symbols are used to assign an expression's result to an output interface signal?
      4. What are the rules used to define an identifier name?
      5. What symbols define a comment line?
      6. Write the statements that will allow a design to access all the contents of the IEEE ARITH library.
      7. Add a 25 ns inertial delay to the XOR assignment statement of question 28.
      8. Make the delay in question 33 transport rather than inertial.
      9. How does a transport delay differ from an inertial delay?
      10. What is the purpose of a SIGNAL declaration?
      11. Where are SIGNAL declarations placed in the design?
      12. Write an assignment statement that assigns the contents of s(5) to t(2).
      13. Write an assignment statement that copies all the states of data_bus to data_in. They are both 8-bit buses.

    11. Process


      1. Statements in an architecture block are executed ________________ .
      2. Statements in a process block are executed ______________.
      3. What is the purpose of a process' sensitivity list?
      4. Under what conditions is a process run?
      5. What is an EVENT?
      6. What is the difference between event and non-event driven process execution?
      7. Write a process block that keeps a running tally of each time an interrupt (INT) signal is asserted high.
      8. Which symbols are used to differentiate between a logic 1 and an integer 1?
      9. Which symbols are used to differentiate between a logic 1011 and an integer 1,011?
      10. What are the results of using CLK'event as a condition in the if statement of the DEF example?

    12. Conditional Statements


      1. In an if..then..else construct, which statements are executed if the condition is TRUE and which if it is FALSE?
      2. What reserved word is used to nest if..then..else statements?
      3. Write the process block that separately tallies positive and negative transitions of the signal TIME_OUT.

    13. Loops


      1. What is the purpose of a for loop?
      2. What are the requirements for a for loop?
      3. Write a process block that uses a for loop to set a zero flag high if all the bits in a sixteen (16) bit word are low (zero).

    14. Structure Design


      1. What is the basic building element of a structure design?
      2. Write the component declaration for the XOR gate in question 21.
      3. Instantiate two copies of the XOR gate in question 57. Gate X1 has inputs Ain and Bin and output I1. Gate X2 has inputs Cin and I1 and output XOR3.
      4. Write the declaration for interconnecting signal I1 in question 58.
      5. Instantiate XOR gate X1 in question 58 using direct association.
      6. Write is the general rule for component declarations?
      7. What is meant by instantiating a component?

    15. Signals


      1. How do signal declarations differ from port interface declarations?
      2. What is the prime use of signals?

    16. Modular Design


      1. What is a module?
      2. Where do modules get their functions and interface signals?

    17. Functions


      1. How many parameters can be passed into a function?
      2. How many results can be returned from a function?
      3. Write a function that returns the sum of two 8-bit words.
      4. How are functions called?
      5. Write a function call for the function in question 69 that adds FIRST to SECOND

      Procedures


      1. .How do procedures differ from functions?
      2. Write a procedure that produces the sum, difference, and product of two integers, WORD1 and WORD2
      3. Write the statement that calls the procedure in question 73 using A_WORD and B_WORD as inputs and TOTAL, DIFF, and TIMES as outputs.

    18. Library Files


      1. Which standard library does not require a library or use statement?
      2. Write the statements to access all the contents of the my_gates section of the my_design library.
      3. What is a PACKAGE?
      4. When is a PACKAGE BODY used?
      5. What is the name of the library used by the current design to store compiled results?
      6. Which standard library is used to access the keyboard and monitor screen?
      7. Write a package declaration called OPERATOR to hold the following items:
        • type op_code ( ADD, SUB, MULT, NULL);
        • constant word_count : integer := 0;
        • constant op_start : op_code := null;
      8. Write the statement to access all of the contents of the package in question 81 from the current design library.

    19. Multiple Architecture Design


      1. Which is the default architecture block in a multiple architecture design?
      2. A design for my_mux has three architecture blocks: mux2bit, mux8bit, and mux16bit. The my_mux design is part of the my_ics library. Write the library and use statements to use the 8-bit mux architecture in a component declaration of my_mux.

prof

Return to site map.

prof

Return to home page.