A VHDL Tutorial for EE 475

Contents

Introduction
A Simple Latch
Synthesis of a 4-Bit Ripple Carry Adder
Design of an 8-Bit Register
Ton-O-Links

Introduction

So what is VHDL? VHDL is an acronym that stands for VHSIC (Very High Speed Integrated Circuit) Hardware Description Language. VHDL is an industry standard for the description, modeling and synthesis of digital circuits and systems. One of the nicest features of this language is that it does not depend on a specific PLA (Programmable Logic Array) or FPGA (Field Programmable Gate Array) for its development. Instead, a VHDL description can be placed in libraries to be used over and over again as technology develops. Another nice aspect of the VHDL language is that it is similar in syntax to objected oriented languages such as C++. But remember, it is not a programming language as we use it. It is a hardware description language.

Contents

Basic Framework and Syntax

On the most basic level, the description of a logical block is split into two parts, the ENTITY and the ARCHITECTURE.

The ENTITY declaration is much like a declaration of a function in C++. In this case, the ENTITY decalaration tells us that we have a device called compare8. It does not tells us how compare8 actually functions... this is left to the ARCHITECTURE section. For example if I were to be describing a 8-bit comparator, I would need two 8-bit inputs and a 1-bit output:

1 ENTITY compare8 IS PORT(
2     x, y:  IN std_logic_vector(7 DOWNTO 0) ;
3     res: OUT std_logic );
4 END compare8;

The first line tells us what we are describing, in this case the name of the entity is compare8. The word PORT followed by a parenthesis tells us that the following information describes the I/O behavior of this entity. Line 2 begins the actual description of our inputs. In this case we are using x and y as inputs and declaring them to be vectors of 8-bits with bit 7 being the most significant and bit 0 the least. Line three describes our output. For a comparison, we only need to know if the values are equal, or not equal. We therefore only need a single bit as the output. The final line tells us that we are at the end of the description for the entity called compare8.

The ARCHITECTURE statement is like the actual function in C++, it describes the logic behind the entity. For the case of the comparator, we want to return a 1 if the two values are equal and a 0 if they are not:

1 ARCHITECTURE struct OF compare8 IS
2 BEGIN
3     res <= '1' WHEN (x = y) ELSE '0';
4 END struct;

The first line tells us that the architecture name of the entity compare8 is struct. This is important because an entity may have several different architectures, perhaps with various levels of detail in their descriptions. For our purposes, however, we will usually only write one architecture. The BEGIN statement tells us that you are beginning your description of the logic. In some cases you may declare types beforehand in which case it would not be the first statement, but we will touch upon that later in this tutorial. Line 3 is the meat of this section, and reads just as it is written: the value 1 will be placed in res when the values of x and y are equal, otherwise it will return a 0. The <= is an assignment operator and can only be used when writing to an output value, variables use a different assignment operator that will be discussed later. The final line tells us that we have completed our description of the architecture struct.

Now even with these 8 lines of code, there is still something missing that will make this a complete program. That is the following:

library ieee;
use ieee.std_logic_1164.all;

These must be the first two lines of every ENTITY because it tells the compiler that you are using the standard IEEE library and that the signal types that you are declaring in the entity can be found in ieee.std_logic_1164.all (this is where it recognizes what std_logic_vector() and std_logic mean). Thus a complete description of an 8-bit comparator would be:

library ieee; 
use ieee.std_logic_1164.all; 
ENTITY compare8 IS PORT(
	x, y:  ;IN  	std_logic_vector(7 DOWNTO 0) ;
	res: ;OUT 	std_logic );
END compare8;

ARCHITECTURE struct OF compare8 IS
BEGIN
	res <= '1' WHEN (x = y) ELSE '0';
END struct;
Contents

A Simple Latch

The simplest latch is the S-R latch, which is described by the following truth table:

Set

Reset

|

Q

/Q

---

---

---

---

---

0

0

|

Q

/Q

0

1

|

0

1

1

0

|

1

0

1

1

|

"X"

"X"

The most important aspect of a latch is that it not only relies on current inputs, but also is dependent on the previous result. In this case, we will have to use a new type of output, the BUFFER. The BUFFER permits the signal to be read and written within the entity. the signal holds its value because of the process. Thus in the ARCHITECTURE section, we can not only write to the outputs, but also use those values in the next iteration.

ENTITY srlatch IS PORT(
     s,r: IN std_logic;
     q,qnot: BUFFER std_logic);
END srlatch;

ARCHITECTURE struct OF srlatch IS
BEGIN
     PROCESS (s,r) BEGIN
          IF (r='1' AND s='0') THEN
              q<='0';
              qnot<= NOT q;
          ELSIF (r='0' and s='1') THEN
              q<='1';
              qnot<= NOT q;
          ELSIF (r='1' and s='1') THEN
              q<='X';
              qnot<='X';
          END IF;
      END PROCESS;
END struct;

This example also introduces the IF...THEN...ELSE structure. As in most programming languages, it is often useful to output different results depending on the input stimulus. In this example, we simply replicate the truth table with if then statements. The ‘X‘ value means unknown. Finally, one of the most important things introduced here is the process statement. The PROCESS statement tells us that the steps within the process are sequential. Now this may be a bit confusing, let us explain… Electronic systems are concurrent by nature, in other words, there is not a sense of order in execution. You can even have multiple processes evaluating concurrently. A sequential circuit is different in the fact that the order of signal assignments affects how the logic is synthesized. It should be noted that when we talk about sequential statements and processes, we are not necessarily talking about sequential logic or logic with memory. To get back to the VHDL… the PROCESS statement tells the compiler that the following steps are to be sequentially executed and therefore, the ultimate synthesis of the logic will be such that the logic will be evaluated in the same manner as a sequential statement.

Contents

Synthesis of a 4-bit ripple carry adder

The purpose of this section is to introduce some more advanced concepts as well as demonstrating a design methodology to be used with VHDL for the synthesis of a logical device.

As you may know, a 4-bit ripple carry adder can be created by simply stringing four 1-bit adders together with a carry path, thus the most logical starting place would be the design of a single bit and the expansion to 4-bits. The truth table of a 1-bit full adder is :

A

B

Carry In

|

Sum

Carry Out

---

---

---

---

---

---

0

0

0

|

0

0

0

0

1

|

1

0

0

1

0

|

1

0

0

1

1

|

0

1

1

0

0

|

1

0

1

0

1

|

0

1

1

1

0

|

0

1

1

1

1

|

1

1

From this we can generate the equations for the sum and the carry. The equation for the sum is:

Sum = A XOR B XOR Carry In

while the equation for the carry is:

Carry Out = (A AND B) OR (B AND Carry In) OR (A AND Carry In)

Once we have derived the boolean equations for a single bit, we can then go on to the actual design of a full adder. From the above we can see three inputs (A, B, and Carry In) and two outputs (Sum, Carry Out), thus our entity declaration would be:

ENTITY addbit IS PORT(
     A, B, Ci : IN std_logic;
     S, Co: OUT std_logic);
END addbit;

The ARCHITECTURE section of this 1-bit entity would be as follows:

ARCHITECTURE struct OF addbit IS
BEGIN
     PROCESS (A, B, Ci)
     BEGIN
          S<= A XOR B XOR Ci;
          Co<= (A AND B) OR (B AND Ci) OR (A AND Ci);
     END PROCESS;
END struct;

Now that we have designed a single bit, we can cascade four by using the hierarchical structure of VHDL to create our 4-bit adder:

library ieee;
use ieee.std_logic_1164.all;
ENTITY add4 IS PORT(
     A, B: INstd_logic_vector(3 DOWNTO 0);
     Cin:IN std_logic;
     Sum: OUT std_logic_vector(3 DOWNTO 0);
     Cout: OUT std_logic);
END add4;

ARCHITECTURE struct OF add4 IS
     COMPONENT addbit
        PORT(
          A, B, Ci : IN  std_logic;
          S, Co: OUT  std_logic);
     END COMPONENT;
     SIGNAL c: std_logic_vector (4 DOWNTO 0);
     BEGIN
     g1: FOR i IN 0 TO 3 GENERATE
          comp: addbit
          PORT MAP(
               A => A(i), 
               B=>B(i),
               S => Sum(i),
               Ci => c(i), 
               Co => c(i+1) );
      END GENERATE g1;
      c(0) <=Cin;
      Cout<=c(4);
END struct;

In this example we introduce four new concepts: the COMPONENT, the SIGNAL, the FOR… GENERATE loop, and the MAP statement. As you may notice, the ENTITY declaration is pretty straightforward so lets skip that and go on to the ARTITECTURE declaration. The first thing you may notice is that the behavior of the 4-bit adder is not directly described; instead we use the COMPONENT statement to say that we are using the previously declared entity addbit (note: the entity addbit must be included in the file containing add4 so that the two are compiled together). In the COMPONENT statement we need to declare which inputs and outputs of the component we will be using. The reason we must declare the component input and output in the architecture section is that these become internal signals to the larger device and thus we do not want them available to outside interaction. This is the same reason that the SIGNAL is declared in the architecture. The SIGNAL statement is like a local variable, but can be thought of as a physical wire inside the device. In this case, we use the signal c to pass along the state of the carry internally. The reason that it is a vector of 5-bits even though we are only using a 4-bit adder is that it reflects the carry path. The statement: FOR i IN 0 TO 3 GENERATE is used to create a loop for mapping the external 4-bit inputs to the single bit inputs of the addbit component. The MAP command as you may have guessed will generate the 1-1 mapping between the I/O of the component being used and the object being built. You will note that the Ci and Co are both mapped to the signal c, but that Carry out of one stage will also be mapped to the carry in stage of the next. Once the mapping is complete, since this 4-bit adder allows a carry in, and produces a carry out, we must then assign the value of the carry in to the first bit in the vector c and the value of the last bit of c to the carry out. This may seem to be a very involved way to create a 4-bit adder, and it is. The purpose of this example is not to minimize the amount of code being written, but to introduce new concepts that will allow you to later create hierarchical designs and reuse code. A much more concise way of building a 4-bit adder would be the following:

library ieee;
USE ieee.std_logic_1164.all;
ENTITY adder IS PORT (
     a, b: IN std_logic_vector (3 DOWNTO 0);
     sum: OUT std_logic_vector (3 DOWNTO 0);
     cout: OUT std_logic);
END adder;

ARCHITECTURE struct OFadder IS
SIGNAL c : std_logic_vector (4 DOWNTO 0);
BEGIN
     interm<=a XOR b;
     c<=((a AND b) OR (interm AND c(3 DOWNTO 0))) & '0';
     sum<= interm XOR c(3 DOWNTO 0);
     cout<=c(4);
END struct;

This adder is a bit different as it does not take a carry in as an argument, but produces the same signal output. It also makes use of the concatination operator "&" to extend the value being placed into c to 5-bits as opposed to 4.

Contents

Design of an 8-bit register

Our last example will be the design of a 4x8 register as shown below:

A VHDL description of the above model is:

library ieee;
USE ieee.std_logic_1164.all;
ENTITY reg8 IS PORT(
     clk,we:         IN std_logic;
     rdata:            IN std_logic_vector (7 DOWNTO 0);
     Asel, Bsel:   IN std_logic_vector (1 DOWNTO 0);
     Aout,Bout:   OUT std_logic_vector (7 DOWNTO 0) );
END reg8;

ARCHITECTURE one OF reg8 IS<
BEGIN
first: PROCESS (clk, we, rdata, Asel, Bsel)
     TYPE reg_array IS ARRAY(0 TO 3) OF std_logic_vector(7 DOWNTO 0);
     VARIABLE reg:reg_array(7 DOWNTO 0);
     BEGIN
          IF clk'EVENT AND clk='0' THEN
               IF (we='1') THEN 
                    CASE Asel IS
                         WHEN "00" => reg(0):=rdata;
                         WHEN "01" => reg(1):=rdata;
                         WHEN "10" => reg(2):=rdata;
                         WHEN OTHERS => reg(3):=rdata; 
                     END CASE;
               ELSE
                     CASE Asel IS
                         WHEN "00" => Aout<=reg(0);
                         WHEN "01" => Aout<=reg(1);
                         WHEN "10" => Aout<=reg(2);
                         WHEN OTHERS => Aout<=reg(3);
                      END CASE;
                      CASE Bsel IS
                         WHEN "00" => Bout<=reg(0);
                         WHEN "01" => Bout<=reg(1);
                         WHEN "10" => Bout<=reg(2);
                         WHEN OTHERS => Bout<=reg(3);
                      END CASE;
               END IF;
           END IF;
     END PROCESS first;
END one;

We introduce three new concepts in this example: TYPE, VARIABLE, and CASE. Earlier we saw something called a SIGNAL which looks like a variable. The difference is that a SIGNAL is analogous to a physical wire while a variable is only visible inside a process, function, or procedure. A variable is typically used as index holders in loops, but can also be used in a similar fashion to a signal. The TYPE definition allows you create new types of variables just as it wold in any other programming language. Since VHDL is a strongly typed language, there are a multitude of types available including integer, array, floating, record, and composite just to name a few. From these types the user can create new types as necessary using the TYPE statement. The last concept introduced here is the CASE statement. The CASE statement is a block of sequential statements that allow the user to dictate the behavior of a part by looking at an input and using the input as a key to determine which path to follow. The "clk'EVENT AND clk='0' " statement evaluates to true when there is a falling edge on the signal clk. Every time a signal changes states, an event occurs, thus in this case, we want the clock to change states and the current state to be '0'.

Contents

Ton-O-Links


Tutorials and VHDL help

The IEEE VHDL Interactive Tutorial: Very thorough tutorial-if you can't learn VHDL from this one ... try one of the others
A Hardware Engineer’s Guide to VHDL: Not a bad tutorial, good if you just don't have the time to spend on the IEEE one
Green Mountain Introductory VHDL Tutorial : Another good tutorial for understanding the basics of VHDL
VHDL STRUCTURAL TUTORIAL: Not as structured as they think... try one of the others first
VHDL Quick Reference: Not so quick a reference if you don't understand VHDL

Other VHDL Related Links

Free VHDL software: TONS of free and evaluation software, note: these programs may or may not help you...
Links to HDL Related Servers: More VHDL links then we have time to follow
Some VHDL Goodies : Links to assorted VHDL information
Standard Analyzer of VHDL Applications for Next-Generation Technology : A multi-college/commercial venture into the VHDL software world
VHDL Technology Group FAQ: Even more VHDL info can be found here

Contents
Copyright Statement