Y. Z.
Ider
Original date: 21-10-2008
Modified: 26-2-2025
INTRODUCTION TO VHDL PROGRAMMING
VHDL programs have parts of code which can be classifed into three types
• Dataflow type VHDL code
• Structural 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 that 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.
Z<=X and not Y;
is a simple signal assignment statement which has the general form:
signal_name <= 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 entity’s 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 U
D V
B Z
A
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.
D
Time
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 neither. 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 t7
A
Time
B
Let us now see how the computer finds U,V,Z.
Consider the fourth input event (t = t4) at which time D goes to ‘0’ from ‘1’.
Just before this event at t = t4- ABCD = 1011, and at t = t4 ABCD = 1010.
Let us see how the computer finds U,V,Z at t = t4, assuming that it has already calculated
them just before at t = t4- . At t = t4- UVZ = 101.
Now at t = t4 we have the values ABCD = 1010 and the old values (t = t4- ) of UVZ =101.
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 had
used U=1, but now U has become 0. We should have used the value of U = 0. We
therefore repeat the evaluations, with ABCD = 1010 and UVZ = 001, 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 the statements 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 righthand side signals 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:
This is the general signal assignment statement and has the form:
signal_name <=
expression when boolean-expression else
expression when boolean-expression else
…
expression when boolean-expression else
expression;
As an example consider the “inhibit” entity 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 don’t care (‘-‘), HighZ (‘Z’) etc. In fact a
standard logic type STD_LOGIC may take values of ‘1’ ,’0’ ,’-‘ ,’Z’, ’U’, ’X’, ’W’, ’L’,
or ’H’. 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 (general assignment statement), and
• with select statement.
STRUCTURAL TYPE VHDL PROGRAMMING
Suppose that we have already designed two modules shown below
X A
Small1
D
B Small2 U
Y
E
C
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
U R
E
P X A
B
Q Y
C D
U S
E
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 R
Large
S
Q
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
U R
K E
P X A
L
B
Q Y
C D
U S
M
E
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
U R
K E
X X A
L
B
Y Y
C D
U S
M
E
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 statement 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 completely
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 exited 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 ;
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 t = 0):
1) x becomes 0, although it was assigned the initial value of 1 for time 0-, because in
proc1 it is assigned the value of d. How do we follow the value of x? We look at w
because w is always equal to x. Why can’t we see x on the graph? Because x is not an
output but w is. This a limitation of the student version of Modelsim, which only displays
input and output signals but not the internal signals. If we had a better simulator we
would not have needed w.
2) res1 is the same as d as we expect. It is assigned the value of d which is 0.
3) res2 is found to be 1 at t = 0. 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 at time 0- ? It is ‘1’. Therefore res2 is ‘1’.
4) res3: 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: 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 (U = “uninitialized”). When we exit proc3, y is 0 but res4 is U. So then what?
Well at exit from proc3 the value of y has changed from the previous U 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 later:
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 event (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.
Uninitialized x:
If in the above example we had used an uninitialized signal x, using
signal x: std_logic ;
instead of
signal x: std_logic :=’1’;
we would have obtained the simulation output below.
As is observed from the simulation, res2 is drawn as a red line with a level between 0 and
1 between time 0 and the first event. This red line indicates that res2 is unknown during
this time, and it is assigned the value ‘U’ meaning “uninitialized”. This is so because the
initial value of x at t = 0- is unknown.
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.
HOW TO ADD DELAYS TO ASSIGNMENT STATEMENTS:
Consider the example
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity after_example is
Port ( A : in std_logic;
F : out std_logic);
end after_example;
architecture Behavioral of after_example is
begin
F<= not A after 125ns;
end Behavioral;
A simulation example is as follows
As you see A becomes 1 at 300 ns but F becomes 0 at 425 ns.
Also note that at t = 0 A is 0 and F is assigned the value 1. However F is ‘U’ at time 0- .
Thus although F is assigned the new value 1 at t = 0, this is scheduled to happen at t =
125 ns. Until that time F =’U’.
However let us look at what happens at 1000 ns. At this time A becomes 1 and therefore
F is scheduled to become 0 at 1125 ns. However before we reach 1125 ns A becomes 0
again meaning that we have changed our mind. Therefore F is not made 0 at all.
Let us review the concept of “event” at this stage. Any change in F is scheduled to a later
time (125 ns later). These scheduled times are also called events. Thus events are not
only the times of input changes, but also the times of output changes.
In the above example we have demonstrated “inertial delay”, meaning that if we change
our mind early enough then the output is not affected.
There is also another delay model in VHDL, namely the “transport delay”:
F<= transport not A after 125ns;
In this case any change in F, once scheduled, takes effect after the delay for sure.
Non-synthesizable versus synthesizable statements:
The delay statements we have used above are non-synthesizable. They cannot be
designed as circuits or implemented in a programmable chip. They are only used if we
are using VHDL for simulation purposes. Other types of non-synthesizable statements are
the “wait” statements, such as “wait for”, “wait on”, “wait until”, which are only used for
simulation in setting up the input waveforms.