next up previous contents
Next: 2.4 Configuration Declaration Up: 2. Basic VHDL Concepts Previous: 2.2 Entity Declaration

Subsections

2.3 Architecture

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.


  
Figure 3: Hierarchical circuit design.
\begin{figure}
\epsfysize=5cm
\hbox to \textwidth{
\hfill
\epsfbox{eps-engl/ar-hier.eps}
\hfill }
\end{figure}

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:

All of these modeling styles share the same organization of an architecture.

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.

2.3.1 Concurrent Behavioral Description

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 $ \Delta$, 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 + $ \Delta$ 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$ \Delta$. 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$ \Delta$. Because no further events are scheduled for a third $ \Delta$, 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.


  
Figure 4: Simulation cycle with delta-delay; B = '0', C = '1'
\begin{figure}
\epsfysize=5,5cm
\hbox to \textwidth{
\hfill
\epsfbox{eps-engl/ar-sim-d.eps}
\hfill }
\end{figure}

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.

concurrent signal assignment statement:
This statement is equal to the ones used in the previous examples.

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.

conditional signal assignment statement:
In this case there are different assignment statements related to one target signal. The selection of one assignment statement is controlled by a set of conditions condition. The conditional signal assignment statement can be compared with the well known if - elsif - else structure.

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.

selected signal assignment statement:
With this statement a choice between different assignment statements is made. The selection of the right assignment is done by the value of select_expression. The statement resembles a case structure.

Syntax:
[label:]
with select_expression select
  signal_name <=expression when value {,
                expression when value};

assertion statement:
This statement serves to generate warnings or error messages during simulation after testing a certain condition. It can be used, for example, to ensure the timing restrictions (setup, hold, ...) are met.

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.

process statement:
A process statement defines a region of code within all statements are executed sequentially. This concept is explained in detail in Section 2.3.2. Here it should be emphasized that every process statement as a whole is treated as a concurrent statement which is executed in parallel with all other concurrent statements.

   
2.3.2 Sequential Behavioral Description

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:

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 + $ \Delta$, 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.

sequential signal assignment statement:
The syntax of a sequential signal assignment is very similar to the concurrent assignment statement, except for a label which can not be used.

Syntax:
signal_name <=
[transport] expression [after time_expr] {,
expression after time_expr};

variable assignment statement:
A variable assignment statement is very similar to a signal assignment. As already mentioned, a variable differs from a signal in that it gets its new value immediately upon assignment. Therefore, the specification of a delay time in a variable assignment is not possible. Attention must be paid to the assignment operator which is := for a variable and <= for a signal.

Syntax:
variable_name := expression;

assertion statement:
Generating error or warning messages is possible also within the process environment. The syntax is nearly identical to a concurrent assertion statement, except for a label which can not be used.

Syntax:
assert condition
  
[report string_expr]
  
[severity failure|error|warning|note];

wait statement:
This statements may only be used in processes without a sensitivity_list. The purpose of the wait statement is to control activation and suspension of the process.

Syntax:
wait
[on signal_names]
      
[until condition]
      
[for time_expression];

The arguments of the wait statement have the following interpretations:

if-elsif-else statement:
This branching statement is equivalent to the ones found in other programming languages and, therefore, needs no further explanation.

Syntax:
if condition then
  sequential_statements
{elsif condition then
  sequential_statements}
[else
  sequential_statements
]
end if;

case statement:
This statement is also identical to its corresponding equivalent found in other programming languages.

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.

null statement:
This statement is used for an explicit definition of branches without any further commands. Therefore, it is used primarily in case statements, and also in if clauses.

Syntax:
null;

loop statement:
is a conventional loop structure found in other programming languages.

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.

exit and next statement:
With these two statements a loop iteration can be terminated before reaching the keyword end loop. With next the remaining sequential statements of the loop are skipped and the next iteration is started at the beginning of the loop. The exit directive skips the remaining statements and all remaining loop iterations. In nested loops both statements skip the innermost enclosing loop if loop_label is left out. Otherwise, the loop labeled loop_label is terminated. The optional condition expression can be specified to determine whether or not to execute these statements.

Syntax:
next
[loop_label][when condition];
exit
[loop_label][when condition];

  
2.3.3 Structural Description

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.


  
Figure 5: Structural implementation of a full adder.
\begin{figure}
\epsfxsize=12cm
\hbox to \textwidth{
\hfill
\epsfbox{eps-engl/ar-st-fa.eps}
\hfill }
\end{figure}

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!


next up previous contents
Next: 2.4 Configuration Declaration Up: 2. Basic VHDL Concepts Previous: 2.2 Entity Declaration
Richard Geissler
1998-10-07