Following the entity declaration, the second important component of a VHDL description is the architecture. This is where the functionality and the internal implementation of a module is described. In general, a complex hierarchically structured system may have the topology shown in Figure 3.
In order to describe such a system both behavioral and structural descriptions are required. A behavioral description may be of either concurrent or sequential type. Overall, VHDL architectures can be classified into the three main types:
Syntax:
architecture architecture_name of entity_name is
[arch_declarative_part]
begin
[arch_statement_part]
end [architecture_name];
As mentioned before, the architecture specifies the implementation of the entity entity_name. A label architecture_name must be assigned to the architecture. In case there are multiple architectures associated with one entity this label is then used within a configuration statement to bind one particular architecture to its entity. The architecture block consists of two parts: the arch_declarative_part before the keyword begin and the arch_statement_part after the keyword begin. In the declaration part local types, signals, components etc. are declared and subprograms are defined. The actual model description is done in the statement part. In contrast to programming languages like C, the major concern of VHDL is describing hardware which primary works in parallel and not in a sequential manner. For this reason, the statements in the arch_statement_part are executed concurrently, or in parallel. However, during the simulation of a VHDL description all concurrent statements are executed on a processor which processes all instructions sequentially. Therefore, a special simulation algorithm is used to achieve a virtual concurrent processing. This algorithm is explained in the following section.
This kind of description specifies a dataflow through the entity based on concurrent signal assignment statements. A structure of the entity is not explicitly defined by this description but can be derived from it. As an example, consider the following implementation of the entity FULLADDER shown in Figure 2.
Example:
architecture CONCURRENT of FULLADDER is
begin
SUM <= A xor B xor C after 5 ns;
CARRY <= (A and B) or (B and C) or (A and C) after 3 ns;
end CONCURRENT;
Two concurrent signal assignment statements describe the model of the entity FULLADDER. The symbol <= indicates the signal assignment. This means that the value on the right side of the symbol is calculated and subsequently assigned to the signal on the left side. A concurrent signal assignment is executed whenever the value of a signal in the expression on the right side changes. In general, a change of the current value of a signal is called an event. Due to the fact that all signals used in this example are declared as ports in the entity declaration (see section 2.2) the arch_declarative_part remains empty.
Information about a possibly existing delay time of the modeled hardware is provided by the after clause. If there is an event on one of the inputs A, B or C at time T, the expression AxorBxorC is computed at this time T, but the target signal (the output SUM) is scheduled to get this new value at time T + 5 ns. The signal assignment for CARRY is handled in exactly the same way except for the smaller delay time of 3 ns. If an explicit information about the delay time is missing then it is assumed to be 0 ns by default. This means that the signal assignment is executed immediately after an event on a signal on the right side is detected and the calculation of the new expression value is performed.
The simulation of concurrent signal assignments is explained with the help of a second example which gives an alternative implementation of the entity FULLADDER.
Example:
architecture CONCURRENT_VERSION2 of FULLADDER is
signal PROD1, PROD2, PROD3 : bit;
begin
SUM <= A xor B xor C; - statement 1
CARRY <= PROD1 or PROD2 or PROD3; - statement 2
PROD1 <= A and B; - statement 3
PROD2 <= B and C; - statement 4
PROD3 <= A and C; - statement 5
end CONCURRENT_VERSION2;
A specification of delay time is missing in each of these signal
assignments. Therefore, the delay time is set to 0 ns. Nevertheless,
during the VHDL simulation the signal assignment is executed after an
infinitesimally small delay time , the so-called delta-delay. This is necessary to execute all concurrent signal
assignment statements in virtually parallel fashion.
To observe what happens during VHDL simulation with concurrent signal
assignments and to understand the delta-delay mechanism, assume
an event on the input signal A which changes its value from a logical
'0' to '1' at time T. For the values of other input signals assume a
constant '0' on input B, and a constant '1' on input C. Due to the event on
input A the statements 1, 3, and 5 are executed. The statement 1 results
in a new value '0' for the signal SUM, the statement 3 leaves the
signal PROD1 unchanged at '0', and the statement 5 calculates a change
from '0' to '1' for PROD3. The new values get assigned at time T +
due to the missing explicit delay time information in the
statements. The resulting event on PROD3 implicates that statement 2
has to be computed now. This causes a change of the signal CARRY from
'0' to '1' which is assigned at time T + 2
. Due to this
incremental computation with delta-cycles all concurrent
statements are executed in virtually parallel manner. Figure
4 illustrates the sequence of signal changes during the
simulation, starting with the event on A at time T and ending with
the event on CARRY at T + 2
. Because no further events are
scheduled for a third
, the system has stabilized and no more
delta-cycles are necessary. All delta-cycles are hidden and do not
appear on signal waveforms obtained during VHDL simulation. Signal
waveforms show the state of signals before and after all delta-cycles
associated with the simulation time T are executed.
The examples presented so far used only one kind of concurrent signal assignments. A set of additional concurrent statements is listed below. This list is not complete but it includes all statements necessary to describe simple VHDL models.
Syntax:
[label:]
signal_name <= [transport] expression [after time_expr] {,
expression [after time_expr]};
Up to now the label was not used. With this element it is
possible to assign a label to the statement which can be useful for
documentation. Furthermore, it is possible to assign several events
with different delay times to the target signal. In this case the
values to be assigned and their delay times have to be sorted in
ascending order. The keyword transport affects the handling of
multiple signal events coming in short time one after another. This
is explained in section 2.6.1.
Syntax:
[label:]
signal_name <= expression when condition else
{expression when condition else}
expression;
Each time one signal either in expression or condition changes its value the complete statement is executed.
Starting with the first condition, the first true one selects
the expression which is computed and the resulting
value is assigned to the target signal signal_name. To make
the above syntax description more clear the optional statements transport and after time_expr are left out.
Syntax:
[label:]
with select_expression select
signal_name <=expression when value {,
expression when value};
Syntax:
[assert_label:]
assert condition
[report string_expr]
[severity failure|error|warning|note];
If the test of the condition results in false then the
message string_expr is displayed. Different severity
levels of the generated message provide control over the VHDL
simulator behavior. Most simulators allow to specify at which
severity level the message is shown and at which level the
simulation gets interrupted.
Sequential behavioral descriptions are based on the process environment. As already mentioned, a process statement as a whole is treated as a concurrent statement within the architecture. Therefore, in the simulation time a process is continuously executed and it never gets finished. The statements within the process are executed sequentially without the advance of simulation time. To ensure that simulation time can move forward every process must provide a means to get suspended. Thus, a process is constantly switching between the two states: the execution phase in which the process is active and the statements within this process are executed, and the suspended state.
The change of state is controlled by two mutually exclusive implementations:
Syntax:
[proc_label:]
process (sensitivity_list)
[proc_declarativ_part]
begin
[sequential_statement_part]
end process [proc_label];
The sensitivity_list is a list of signal names within round brackets, for example (A, B, C).
Syntax:
[proc_label:]
process
[proc_declarativ_part]
begin
[sequential_statements]
wait ...; - at least one wait statement
[sequential_statements]
end process [proc_label];
The structure of a process statement is similar to the structure of an architecture. In the proc_declarativ_part various types, constants and variables can be declared; functions and procedures can be defined. The sequential_statement_part contains the description of the process functionality with ordered sequential statements.
An implementation of the full adder from Figure 2 with a sequential behavioral description is given below:
Example:
architecture SEQUENTIAL of FULLADDER is
begin
process (A, B, C)
variable TEMP : integer;
variable SUM_CODE : bit_vector(0 to 3) := "0101";
variable CARRY_CODE : bit_vector(0 to 3) := "0011";
begin
if A = '1' then TEMP := 1;
else TEMP := 0;
end if;
if B = '1' then TEMP := TEMP + 1;
end if;
if C = '1' then TEMP := TEMP + 1;
end if; - variable TEMP now holds the number of ones
SUM <= SUM_CODE(TEMP);
CARRY <= CARRY_CODE(TEMP);
end process;
end SEQUENTIAL;
The functionality of this behavioral description is based upon a temporary variable TEMP which counts the number of ones on the input signals. With this number one element, or one bit, is selected from each of the two predefined vectors SUM_CODE and CARRY_CODE. The initialization of these two vectors reflects the truthtable of a full-adder module.
The reason for this unusual coding is the attempt to explain the characteristics of a variable. A variable differs not only in the assignment operator (:=) from that of a signal (<=). It is also different with respect to time when the new computed value becomes valid and, therefore, readable to other parts of the model. Every variable gets the new calculated value immediately, whereas the new signal value is not valid until the beginning of the next delta-cycle, or until the specified delay time elapses.
If the above example had been coded with a signal as the temporary
counter instead of the variable, then the correct functionality of
this architecture as a full adder could not be ensured. After an event
at time T on one of the input signals A, B or C,
which are members of the sensitivity_list, the process is
executed once. Now, assume that TEMP is declared as a signal. In the first if statement the signal TEMP is
either reset to zero or in case A = '1' it is set to '1'. The
assignment of the new value is scheduled for time T + ,
which means that the appropriate event is written to an event queue
for signal TEMP. The simulation continues with executing the
second if statement at time T because computing a
sequential statement does not advance the simulation time. Therefore,
the signal TEMP still holds the same value it had before the
process activation! This means that the intended counting of ones does
not work with TEMP declared as signal.
In general, signal assignment statements within a process have to be
handled with care, especially if the target signal will be read or
rewritten in the following code before the process gets suspended (at
the wait statement or, if a sensitivity list exists, at the end
of the process). If this effect is taken into consideration, the
process statement provides an environment in which a person familiar
with programming languages like C or Pascal can easily generate a VHDL
behavioral description. This remark, however, should not be understood
that the process statement is there for people switching to VHDL. In
reality, some functions can be implemented much more easily in a
sequential manner. As an example, the implementation of a register
belonging to the entity declaration on page is
shown:
Example:
architecture SEQUENTIAL of DFF is
begin
process (CLK, NR)
begin
if (NR = '0') then
- Reset: assigning "000...00" to the
- parameterized output signal Q
Q <= (others => '0');
elsif (CLK'event and CLK = '1') then
Q <= D;
end if;
end process;
end SEQUENTIAL;
Not explained until now is the use of attributes. In the above example, the attribute CLK'event is used to detect an edge on the CLK signal. This is equivalent to an event on CLK. The ability to detect edges on signals is based upon the storage of all events in event queues for every signal. Therefore, old values can be compared with the actual ones or even read. In contrast, variables always get the new assigned value immediately and the old value is not stored. Subsequently, during the simulation more memory is required for a signal for a variable. In complex system descriptions this fact should be taken into consideration.
Generally speaking, attributes exist not only in conjunction with
signals. For instance, there are attributes associated with types and
arrays. Some additional information on attributes is found in Section
7.4 on page .
Due to the similarity between sequential assignment statements in VHDL and common statements in other programming languages, only a brief description of their syntax is provided here.
Syntax:
signal_name <= [transport] expression [after time_expr] {,
expression after time_expr};
Syntax:
variable_name := expression;
Syntax:
assert condition
[report string_expr]
[severity failure|error|warning|note];
Syntax:
wait [on signal_names]
[until condition]
[for time_expression];
The arguments of the wait statement have the following interpretations:
Syntax:
if condition then
sequential_statements
{elsif condition then
sequential_statements}
[else
sequential_statements]
end if;
Syntax:
case expression is
{when choices => sequential_statements}
[when others => sequential_statements]
end case;
Either all possible values of expression must be covered with
choices or the case statement has to be completed with
an others branch.
Syntax:
null;
Syntax:
[loop_label:]
while condition loop | -controlled by condition
for identifier in value1 to|downto value2 loop | -with counter
loop -endless loop
sequential_statements
end loop [loop_label];
In the for loop the counter identifier is automatically declared. It is handled as a local variable within the loop statement. Assigning a value to identifier or reading it outside the loop is not possible.
Syntax:
next [loop_label][when condition];
exit [loop_label][when condition];
In structural descriptions the implementation of a system or model is described as a set of interconnected components, which is similar to drawing schematics. Such a description can often be generated with a VHDL netlister in a graphical development tool. Since there are many different ways to write structural descriptions, to explain all of them in one section would be more confusing than enlightening. Therefore, only one alternative approach is presented here.
As an introductive example, consider the implementation of a
full-adder circuit shown in Figure 5. The corresponding
entity declaration was discussed in Section 2.2 on
page . The components HA and XOR are
assumed to be predefined elements.
Example:
architecture STRUCTURAL of FULLADDER is
signal S1, C1, C2 : bit;
component HA
port (I1, I2 : in bit; S, C : out bit);
end component;
component XOR
port (I1, I2 : in bit; X : out bit);
end component;
begin
INST_HA1 : HA
port map (I1 => B, I2 => C, S => S1, C => C1);
INST_HA2 : HA
port map (I1 => A, I2 => S1, S => SUM, C => C2);
INST_XOR : XOR
port map (I1 => C2, I2 => C1, X => CARRY);
end STRUCTURAL;
In the declarative part of the architecture (the part between the keywords is and begin), all objects which are not yet known to the architecture have to be declared. In the example above, these are the signals (S1, C1 and C2) used for connecting the components together, excluding the ports of the entity FULLADDER. In addition, the components HA and XOR have to be declared. The declaration of a component consists of declaring its interface ports and generics to the actual model.
Often used components could be selected from a library of gates defined in a package and linked to the design. In this case the declaration of components usually is done in the package, which is visible to the entity. Therefore, no further declaration of the components is required in the architecture declarative part.
The actual structural description is done in the statement part of the architecture (between the keywords begin and end arch_name) by the instantiation of components. The components' reference names INST_HA1, INST_HA2 and INST_XOR, also known as instance names, must be unique in the architecture. The port maps specify the connections between different components, and between the components and the ports of the entity. Thus, the components' ports (so-called locals) are mapped to the signals of the architecture (so-called actuals) including the signals of the entity ports. For example, the input port I1 of the half adder INST_HA1 is connected to the entity input signal B, input port I2 to C, and so on.
The instantiation of a component is a concurrent statement. This means that the order of the instances within the VHDL code is of no importance.
Syntax:
component declaration:
component component_name
[generic (generic_list: type_name [:= expression] {;
generic_list: type_name [:= expression]} );]
[port ( signal_list: in|out|inout|buffer type_name {;
signal_list: in|out|inout|buffer type_name} );]
end component;
component instantiation:
component_label: component_name
port map (signal_mapping);
The syntax of a component declaration statement consists of a general
specification of generics and ports which were discussed
in Section 2.2 in reference to the entity
declaration. The connection of the architecture's signals to the
ports of the components can be done in various ways. The syntax used
in the above example makes the assignment in the following way:
Syntax:
signal_mapping: declaration_name => signal_name
It is important to note that the symbol '=>' is used within a port map in contrast to the symbol '<=' used for concurrent or sequential signal assignment statements!