Você está na página 1de 17

07-03-2007 (modified on 21-3-2007) INTRODUCTION TO VHDL PROGRAMMING VHDL programs have parts of code which can be classifed into

three types Structural type VHDL code Dataflow type VHDL code Behavioral type VHDL code A complete VHDL program may contain a mixture (combination) of these three types of codes. In the following we shall first see dataflow type VHDL programming and then continue with the other types. DATAFLOW TYPE VHDL PROGRAMMING: Simple assignment statement:
Library IEEE; use IEEE.std_logic_1164.all entity inhibit is port(X,Y: in BIT; Z: out BIT); end inhibit; architecture arch_inhibit of inhibit is begin Z<=X and not Y; end arch_inhibit;

This example defines a circuit with inputs X,Y and output Z, such that Z=XY. X,Y,Z are of BIT type meaning that they can take values of 1 or 0 only. Input and output variables are called signals because they are actual signals which we can observe on an oscilloscope. Library IEEE; use IEEE.std_logic_1164.all are statements written to include (import) definitions of key words, operators, and functions (procedures) which are used in the program. The signal assignment statement has a general form which is explained below:
signal_name <= expression; or signal_name <= expression when boolean-expression else expression when boolean-expression else expression when boolean-expression else expression;

Concept of concurrency: Let us consider a slightly more complex example with more than one simple assignmemt statements, to describe the function Z=A+B(C+D).
Library IEEE; use IEEE.std_logic_1164.all entity circuit is port(A,B,C;D: in BIT; Z: out BIT); end circuit; architecture arch_ circuit of circuit is begin Z<=A or B and (not C or D); end arch_ circuit;

Note that since the order of precedence of the operators is not, and, or we do not have to use an additional pair of parentheses as in Z<=A or (B and (not C or D)). In the example above we still have only one simple assignment statement. Let us now assume that we need to be able to observe some intemediate signals on the oscilloscope (for various reasons) but which are not input or output. Say these are U= not C or D, and or V=B and (not C or D) (alternatively V=B and U). In such a case we must define U and V as signals but only in the architecture, not in the entitys port statement. The following circuit illustrates the point. Here U and V are actual signals ( wires in Verilog) which can be observed on a scope. The computer may not design the circuit exactly as shown below but it makes sure that U and V are available as gate outputs (wires).

C D B A

U V Z

The code which includes U and V is as follows:

Library IEEE; use IEEE.std_logic_1164.all

entity circuit is port(A,B,C;D: in BIT; Z: out BIT); end circuit; architecture arch_ circuit of circuit is signal U,V:BIT; begin U <= not C or D; V <= B and U; Z<=A or V; end arch_ circuit;

The three statements in the architecture are called concurrent statements. They are not sequential as in a standard computer language such as Matlab or Java. These three statements must be viewed as executed at the same time, because in actual circuits with many gates, all gates operate at the same time (simultaneously). It follows therefore that their order of being written in the program must not make any difference. Thus the following program is equally acceptable.
Library IEEE; use IEEE.std_logic_1164.all entity circuit is port(A,B,C;D: in BIT; Z: out BIT); end circuit; architecture arch_ circuit of circuit is signal U,V:BIT; begin V <= B and U; Z<=A or V; U <= not C or D; end arch_ circuit;

How concurrency is handled in a simulation: Consider the following input waveforms.

A Time B C D

The computer is asked to make a simulation of the circuit described by the above VHDL code, that is , to find the output and intermediate signal waveforms for the given input waveforms. The dashed vertical lines in the above figure show the input events. They are the time instants at which there is any change in the inputs. In between the events, inputs do not change. The computer calculates U,V,Z for discrete times. In Xilinx program the time difference between successive samples can be as small as 1 psec. In doing so, once it calculates U,V,Z just after an event, it does not change them until the next event, because if the inputs are not changing then U,V,Z would not change either. This kind of calculation is called event-based simulation and saves the computer considerable amount of time and memory. Obviously the simulation result must be as follows (assuming for the moment that gates do not have delays):

t4 A

t7

Time B C D

U V Z

Let us now see how the computer finds U,V,Z. Consider the fourth input event (t4) at which time D goes to 0 from 1. Just before this event ABCD = 1011, and just after it ABCD = 1010. Let us see how the computer finds U,V,Z just after this event, assuming that it has already calculated them just before. Just before the event UVZ = 101. Now we have the values ABCDUVZ=1010101 where UVZ = 101 are the old values. We use these values at the righthand sides of the statements to find the lefthand side values. We get UVZ=001. However in executing the V <= B and U statement we used U=1, but now U became 0. We should have used the value of U=0. We therefore repeat the evaluations, with ABCDUVZ=1010001, and we again get UVZ=001. We stop and accept UVZ=001. In this example two runs were sufficient, but in general we repeat the execution of the statements until the values of signals on the lefthand side of the statements do not change any more. Now let us consider what happens at the 7th input event (t7). Just before the event ABCDUVZ = 0110000. Just after the event ABCDUVZ = 0111xxx. Calculating UVZ we get ABCDUVZ = 0111100. In doing this we use the new values of A,B,C,D and the old values of U,V,Z. Calculating again we get ABCDUVZ = 0111110. Calculating again we

get ABCDUVZ = 0111111. Calculating again we again get ABCDUVZ = 0111111, and we stop. Conclusion: For each simulation point the program calculates the statements repeatedly until a stable solution is obtained. This stable solution is then assigned as the result for that simulation point. Thus although the computer in essence evaluates the statements sequentially, by way of the repeated procedure they appear to have been executed concurrently. Actually in the first run all statements are executed in any order. In the second or later runs only the statements for which there is some change in the lefthand sides are executed. Sometimes we may not arrive at a stable solution. For example consider the statement Y<=Y. Each time we execute it the value of Y changes, and a stable solution is never reached. In such a case the computer terminates the execution when a predetermined number of cycles is reached ( say 1000) and reports an error.

when else satement: Consider the inhibit example again:


Library IEEE; use IEEE.std_logic_1164.all entity inhibit is port(X,Y: in BIT; Z: out BIT); end inhibit; architecture arch_inhibit of inhibit is begin Z<= 1 when X=1 and Y=0 else 0; end arch_inhibit;

with select statement: In this example we want to find the prime numbers between 0 and 15 inclusive. Wakerly takes 1 as a prime number as well. Thus the prime numbers are 1,2,3,5,7,11,13. Numbers from 0 to 15 can be represented by 4-bit binary numbers. Thus the input of the circuit is a 4-bit binary variable, N=N3N2N1N0, and the output is say F which is 1 if the input is prime and 0 otherwise.
Library IEEE; use IEEE.std_logic_1164.all entity prime is

port(N: in STD_LOGIC_VECTOR (3 downto 0); F: out STD_LOGIC); end prime; architecture arch_ prime of prime is begin with N select Z<=1 when 0001, 1 when 0010, 1 when 0011|0101|0111, 1 when 1011|1101, 0 when others; end arch_ prime;

| means or. This was an example to illustrate the use of the with select statement. We could have written the with select statement shorter like
with N select Z<=1 when 0001|0010 |0011|0101|0111| 1011|1101, 0 when others;

The others key word is necessary to cover all other cases for the value of N. In the code above others covers all values of N which are not prime, but it also covers all other values for which one or more of the bits may be dont care (-), HighZ (Z) etc. In fact a standard logic type STD_LOGIC may take values of 1,0,-,Z and other 4 values which we do not use. In contrast types of BIT may only take values of 1 or 0. Note that in the above example we could have used
entity prime is port(N: in BIT_VECTOR (3 downto 0); F: out BIT); end prime;

Sometimes we may use some functions found in the library (or self-written) to shorten our code:
Library IEEE; use IEEE.std_logic_1164.all entity prime is port(N: in STD_LOGIC_VECTOR (3 downto 0); F: out STD_LOGIC); end prime; architecture arch_ prime of prime is begin with CONV_INTEGER(N) select Z<=1 when 1|2|3|5|7|11|13, 0 when others; end arch_ prime;

Here CONV_INTEGER(N) converts a binary number N to integer. There is another closely related function CONV_STD_LOGIC_VECTOR. Thus CONV_STD_LOGIC_VECTOR(I,M) converts the integer I to an M-bit binary number.

In summary dataflow type statements are simple assignment statement, when else statement, and with select statement. STRUCTURAL TYPE VHDL PROGRAMMING Suppose that we have already designed two modules shown below

X Y

Small1

A B C D E Small2 U

The Small1 module has two inputs X, Y and three outputs A,B,C. The Small2 module has two inputs D, E and one output U. We wish to use these modules to design a larger circuit as shown

Large D P Q X Y A B C D E U S E U R

Obviously the larger circuit called Large, which is shown in a broken rectangle, has two inputs P, Q and two outputs R, S. It can also be viewed as

P Q

Large

R S

The Large circuit which can also be called the top module uses one instance of Small1 and two instances of Small2. Since the cables connecting Small1 to Small2s represent actual signals let us also label them as K,L,M. Large D P Q X Y A B C M K L D E E U R

We must first write code for Small1 and Small2 and then for Large.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity small1 is Port ( X,Y: in std_logic; A,B,C : out std_logic); end small1; architecture Behavioral of small1 is begin A<=X; B<=Y; C<=X or Y; end Behavioral;

library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity small2 is Port ( D,E : in std_logic; U : out std_logic); end small2; architecture Behavioral of small2 is begin U<=D and E; end Behavioral;

If the codes for Small1 and Small2 are written in the same project, they are available to Large and they do not have to be included in the library.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity LARGE is Port ( P : in std_logic; Q : in std_logic; R : out std_logic; S : out std_logic); end LARGE; architecture Behavioral of LARGE is component small1 port (X,Y: in std_logic; A,B,C : out std_logic); end component ; component small2 port ( D,E : in std_logic; U : out std_logic); end component ; signal K,L,M: std_logic; begin LABEL1: small1 port map ( P,Q,K,L,M); LABEL2: small2 port map (K,L,R); LABEL3: small2 port map (L,M,S); end Behavioral;

Note that we have used different names for all inputs and outputs. However in writing the code for Large note that the X,Y of Small1 are internal to Small1. Therefore for P, Q we could have used X, Y as well.

Large D X Y X Y A B C M K L D E E U R

In this case the code for large would be


library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity LARGE is Port ( X : in std_logic; Y : in std_logic; R : out std_logic; S : out std_logic); end LARGE; architecture Behavioral of LARGE is component small1 port (X,Y: in std_logic; A,B,C : out std_logic); end component ; component small2 port ( D,E : in std_logic; U : out std_logic); end component ; signal K,L,M: std_logic; begin LABEL1: small1 port map ( X,Y,K,L,M); LABEL2: small2 port map (K,L,R); LABEL3: small2 port map (L,M,S); end Behavioral;

Another point to be careful about is that the order of signals in a component instantiation must be the same as the order of signals in the port map of the component. If you wish to be more relaxed and do not want to care about the order you can use for example the following code
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity LARGE is Port ( X : in std_logic; Y : in std_logic; R : out std_logic; S : out std_logic); end LARGE; architecture Behavioral of LARGE is component small1 port (X,Y: in std_logic; A,B,C : out std_logic); end component ; component small2 port ( D,E : in std_logic; U : out std_logic); end component ; signal K,L,M: std_logic; begin LABEL1: small1 port map ( X=>X,A=>K,B=>L,C=>M,Y=>Y); LABEL2: small2 port map (D=>K,U=>R,E=>L); LABEL3: small2 port map (U=>S,D=>L,E=>M); end Behavioral;

If more than one component instantiation is present then they must be labeled with different labels.

BEHAVIORAL TYPE VHDL PROGRAMMING Sometimes we need to write code which we want to be sequential. In such cases we use the process statement which is the basis of behavioral type programming. In the architecture of a certain entity we may have many dataflow type statements, many component instantiations and many process statements. Each component instantiation and each process statement is a single concurrent statement. If more than one process statement is present then they must be labeled with different labels. At the beginning of a simulation all concurrent statements are evaluated once in any order. This means the process statements are also evaluated once at the beginning of a simulation. However for a process statement to be evaluated later again it is necessary that one or more of the signals in its sensitivity list are changed. Consider our previous example again:
Library IEEE; use IEEE.std_logic_1164.all entity circuit is port(A,B,C;D: in BIT;Z: out BIT); end circuit; architecture arch_ circuit of circuit is signal U,V:BIT; begin V <= B and U; Z<=A or V; U <= not C or D; end arch_ circuit;

Here we have 3 concurrent statements. Let us write the third one as a process:
Library IEEE; use IEEE.std_logic_1164.all entity circuit is port(A,B,C;D: in BIT;Z: out BIT); end circuit; architecture arch_ circuit of circuit is signal U,V:BIT; begin V <= B and U; Z<=A or V; process (C,D) begin U <= not C or D; end process; end arch_ circuit;

This was a dummy example and does not show the strength of the process statement. The purpose of this example was to show that any concurrent statement can be written as a process. It also means that a process statement is by itself a single concurrent statement. Thus in the above example we still have 3 concurrent statements. Let us change the code a little bit:
Library IEEE; use IEEE.std_logic_1164.all entity circuit is port(A,B,C;D: in BIT;Z: out BIT); end circuit; architecture arch_ circuit of circuit is signal U,V:BIT; begin V <= B and U; Z<=A or V; process (C,D) variable G: bit; begin G:=not C; U <= G or D; end process; end arch_ circuit;

Here G is a variable and assignment to a variable is made using the sysmbol :=. When the code in a process statement is executed it is done sequentially and the values for variables are assigned immediately. Thus the value for G is calculated and is used in the following statement U <= G or D. If G were a signal we would obtain a completelety different bahavior as illustrated in the following examples. In evaluating a process statement all statements are evaluated sequentially only once and at the end if some signals have changed then in exiting the process statement the signals are assigned their new values (but not as the evaluation is being continued). If however a signal which appears in the sensitivity list has changed during the evaluation of the process statement then the process statement is evaluated again, until the signals in the sensitivity list do not change any more. Thus consider
Library IEEE; use IEEE.std_logic_1164.all entity circuit is port(A,B,C;D: in BIT;Z: out BIT); end circuit; architecture arch_ circuit of circuit is signal U,V:BIT; signal G: bit;

begin V <= B and U; Z<=A or V; process (C,D) begin G<=not C; U <= G or D; end process; end arch_ circuit;

This code is wrong because since G is a signal the value assigned to it in the G<=not C; statement is valid only after the process is excited and therefore in the statement U <= G or D; the old value of G is used. To correct this code we can add the signal G to the sensitivity list as process (C,D,G). In such a case if in the first run the value of G changes then the process is run again and thus the correct value of G is used in the U <= G or D; statement. Let us now consider another example where what we include in the sensitivity list may change the timing simulation drastically:
library ieee; use ieee.std_logic_1164.all; entity init is port(d:in std_logic; res1,res2,res3,res4,w:out std_logic); end init; architecture behv of init is signal x: std_logic :='1'; signal y: std_logic :='1'; begin res1<=d; w<=x; proc1: process(d) begin x<=d; res2 <= x; end process; proc2: process(d) variable z: std_logic; begin z:= d; res3 <= z; end process; proc3: process(d,y) begin y <= d;

res4 <= y; end process; end behv;

This program has 5 concurrent statements. First as a note consider the statement signal x: std_logic :='1'; This statement defines a signal x and also it says that the initial value of x is 1 at the beginning of the simulation (at time = 0). It does not mean that the value of x is always 1, it may change during the simulation. If you want to define something which always has the same value you should use the constant statement. In the following figure we have the result of simulation.

At the beginning of a simulation (time = 0) all concurrent statements are executed. Later they are executed only if either the right hand side changes or something in the sensitivity list changes. There are many useful observations that we can make: Let us first see what happens at the beginning (time = 0). 1) x becomes 0 although it was assigned the initial value of 1 because in proc1 it is assigned the value of d. res1 is the same as d as we expect. How do we follow the value of x? We look at w because w is always equal to x. Why cant we see x on the graph? Because x is not an output but w is. 2) res1 is OK. It is assigned the value of d which is 0. 3) res2 has a problem. It is drawn as unknown ( not 1 or 0). Why? If you look at proc1 you see that x is assigned the value of 0 but its value is not updated yet. It will be updated when proc1 is exited. In the second statement of proc1 res2 is found. In doing so the old value of x is used. But what is the old value of x, that is, what is its value before time < 0. We do not know. Therefore res2 is also unknown. 4) res3 seems OK. When proc2 is executed first the variable z is assigned the value of d which is 0. variables are different then signals. Whenever they are assigned a value this takes effect immediately. That is we do not have to wait to exit the process. In the second statement of proc2 res3 is assigned the value of z which is 0. 5) res4 also seems OK. In proc3 first signal y is made 0 but this value does not take effect yet. In the second statement of proc3 res4 is assigned to the old (time < 0) value of y which is unknown. When we exit proc3, y is 0 but res4 is unknown. So then what? Well at exit from proc3 the value of y has changed from the previous unknown value to 0, and therefore proc3 must be executed again because y is in the sensitivity list of proc3. When proc3 is executed again y is again assigned the value 0 and now res4 is also assigned the value 0. No more need to repeat proc3. Let us see what happens at time = 100ns. If you follow the graphs carefully you will see that res1, res3, res4 and w follow the value of d as expected but res2 follows the previous value of x. Thus at any ever (meaning a change in d) res2 takes the previous value of x. Thus if we want the output to follow d exactly we must do either one of the following. Use a variable as in proc2 or include the intermediate signal into the sensitivity list of the process as we have done for proc3.

Above we have reviewed important concepts such as initialization of signals, use of variables in a process, and the importance of the sensitivity list as well as that statements in a process are sequential but new signal values take effect at exit. In a process statement you can also use all common constructs such as if-else-then, case-when, loop, for-loop, while, and simple assignments. These constructs are for you to learn from the textbook.

Você também pode gostar