Chapter 3. Sequential Statements

In the previous chapter we saw how to represent the internal state of models using VHDL data types. In this chapter we look at how that data may be manipulated within processes. This is done using sequential statements, so called because they are executed in sequence. We have already seen one of the basic sequential statements, the variable assignment statement, when we were looking at data types and objects. The statements we look at in this chapter deal with controlling actions within a model; hence they are often called control structures. They allow selection between alternative courses of action as well as repetition of actions.

If Statements

In many models, the behavior depends on a set of conditions that may or may not hold true during the course of simulation. We can use an if statement to express this behavior. The syntax rule for an if statement is

   if_statement ⇐
      [ if_label : ]
      if condition then
          { sequential_statement }
      { elsif condition then
          { sequential_statement } }
      [ else
          { sequential_statement } ]
      end if [ if_label ] ;

At first sight, this may appear somewhat complicated, so we start with some simple examples and work up to examples showing the general case. The label may be used to identify the if statement. We will discuss labeled statements in Chapter 20. A simple example of an if statement is

   if en then
    stored_value := data_in;
   end if;

The condition after the keyword if is used to control whether or not the statement after the keyword then is executed. If the condition is true, the statement is executed. In this example, if the value of the object en is ‘1’, the assignment is made; otherwise it is skipped. We can also specify actions to be performed if the condition is false. For example:

   if sel = 0 then
    result <= input_0;  -- executed if sel = 0
   else
    result <= input_1;  -- executed if sel /= 0
   end if;

Here, as the comments indicate, the first signal assignment statement is executed if the condition is true, and the second signal assignment statement is executed if the condition is false.

In many models, we may need to check a number of different conditions and execute a different sequence of statements for each case. We can construct a more elaborate form of if statement to do this, for example:

   if mode = immediate then
    operand := immed_operand;
   elsif opcode = load or opcode = add or opcode = subtract then
    operand := memory_operand;
   else
    operand := address_operand;
   end if;

In this example, the first condition is evaluated, and if true, the statement after the first then keyword is executed. If the first condition is false, the second condition is evaluated, and if it evaluates to true, the statement after the second then keyword is executed. If the second condition is false, the statement after the else keyword is executed.

In general, we can construct an if statement with any number of elsif clauses (including none), and we may include or omit the else clause. Execution of the if statement starts by evaluating the first condition. If it is false, successive conditions are evaluated, in order, until one is found to be true, in which case the corresponding statements are executed. If none of the conditions is true, and we have included an else clause, the statements after the else keyword are executed.

We are not restricted to just one statement in each part of the if statement. This is illustrated by the following if statement:

   if opcode = halt_opcode then
    PC := effective_address;
    executing := false;
    halt_indicator <= true;
   end if;

If the condition is true, all three statements are executed, one after another. On the other hand, if the condition is false, none of the statements are executed. Furthermore, each statement contained in an if statement can be any sequential statement. This means we can nest if statements, for example:

   if phase = wash then
    if cycle_select = delicate_cycle then
      agitator_speed <= slow;
    else
      agitator_speed <= fast;
    end if;
    agitator_on <= true;
   end if;

In this example, the condition phase = wash is first evaluated, and if true, the nested if statement and the following signal assignment statement are executed. Thus the assignment agitator_speed <= slow is executed only if both conditions evaluate to true, and the assignment agitator_speed <= fast is executed only if the first condition is true and the second condition is false.

Example 3.1. A heater thermostat

Let us develop a behavioral model for a simple heater thermostat. The device can be modeled as an entity with two integer inputs, one that specifies the desired temperature and another that is connected to a thermometer, and one Boolean output that turns a heater on and off. The thermostat turns the heater on if the measured temperature falls below two degrees less than the desired temperature, and turns the heater off if the measured temperature rises above two degrees greater than the desired temperature. The entity and architecture body for the thermostat are:

   entity thermostat is
    port ( desired_temp, actual_temp : in integer;
           heater_on : out boolean );
   end entity thermostat;
   --------------------------------------------------
   architecture example of thermostat is
   begin
    controller : process (desired_temp, actual_temp) is
    begin
      if actual_temp < desired_temp - 2 then
        heater_on <= true;
      elsif actual_temp > desired_temp + 2 then
        heater_on <= false;
      end if;
    end process controller;

   end architecture example;

The entity declaration defines the input and output ports. Since it is a behavioral model, the architecture body contains only a process statement that implements the required behavior. The process statement includes a sensitivity list after the keyword process. This is a list of signals to which the process is sensitive. When any of these signals changes value, the process resumes and executes the sequential statements. After it has executed the last statement, the process suspends again. In this example, the process is sensitive to changes on either of the input ports. Thus, if we adjust the desired temperature, or if the measured temperature from the thermometer varies, the process is resumed. The body of the process contains an if statement that compares the actual temperature with the desired temperature. If the actual temperature is too low, the process executes the first signal assignment to turn the heater on. If the actual temperature is too high, the process executes the second signal assignment to turn the heater off. If the actual temperature is within the range, the state of the heater is not changed, since there is no else clause in the if statement.

VHDL-87, -93, and -2002

These versions of VHDL do not perform implicit conversion of conditions to boolean. Conditions in if statements must yield boolean results without conversion. Hence, we must write the if statement on page 66 as:

   if en = '1' then
    ...
   end if;

VHDL-87

If statements may not be labeled in VHDL-87.

Conditional Variable Assignments

In many models, we want to assign different values to a variable depending on one or more conditions. We could write this using an if statement, with each part containing a different assignment to the variable. However, VHDL provides a shorthand notation, called a conditional variable assignment, that we can use in these cases. The syntax rule is

   conditional_variable_assignment ⇐
      [ label : ]
      name := expression when condition
              { else expression when condition }
              [ else expression ] ;

For example, we could write:

   result := a - b when mode = subtract else a + b;

instead of the longer equivalent if statement:

   if mode = subtract then
    result := a - b;
   else
    result := a + b;
   end if;

Of course, if we need to perform more than one assignment for each condition, or if the actions required are not assignments, we would still use an if statement.

VHDL-87, -93, and -2002

These versions of VHDL do not provide the conditional variable assignment shorthand notation. We must write an if statement with a separate variable assignment for each condition.

Case Statements

If we have a model in which the behavior is to depend on the value of a single expression, we can use a case statement. The syntax rules are as follows:

   case_statement ⇐
      [ case_label : ]
      case expression is
          ( when choices => { sequential_statement } )
          { ... }
      end case [ case_label ] ;
   choices ⇐ ( simple_expression | discrete_range | others ) { | ... }

The label may be used to identify the case statement. We will discuss labeled statements in Chapter 20. We start with some simple examples of case statements and build up from them. First, suppose we are modeling an arithmetic/logic unit, with a control input, func, declared to be of the enumeration type:

   type alu_func is (pass1, pass2, add, subtract);

We could describe the behavior using a case statement:

   case func is
    when pass1 =>
      result := operand1;
    when pass2 =>
      result := operand2;
    when add =>
      result := operand1 + operand2;
    when subtract =>
      result := operand1 - operand2;
   end case;

At the head of this case statement is the selector expression, between the keywords case and is. In this example it is a simple expression consisting of just a primary value. The value of this expression is used to select which statements to execute. The body of the case statement consists of a series of alternatives. Each alternative starts with the keyword when and is followed by one or more choices and a sequence of statements. The choices are values that are compared with the value of the selector expression. There must be exactly one choice for each possible value. The case statement finds the alternative whose choice value is equal to the value of the selector expression and executes the statements in that alternative. In this example, the choices are all simple expressions of type alu_func. If the value of func is pass1, the statement result := operand1 is executed; if the value is pass2, the statement result := operand2 is executed; and so on.

A case statement bears some similarity to an if statement in that they both select among alternative groups of sequential statements. The difference lies in how the statements to be executed are chosen. We saw in the previous section that an if statement evaluates successive Boolean expressions in turn until one is found to be true. The group of statements corresponding to that condition is then executed. A case statement, on the other hand, evaluates a single selector expression to derive a selector value. This value is then compared with the choice values in the case statement alternatives to determine which statement to execute. An if statement provides a more general mechanism for selecting between alternatives, since the conditions can be arbitrarily complex Boolean expressions. However, case statements are an important and useful modeling mechanism, as the examples in this section show.

The selector expression of a case statement must result in a value of a discrete type, or a one-dimensional array of character elements, such as a character string or bit string (see Chapter 4). Thus, we can have a case statement that selects an alternative based on an integer value. If we assume index_mode and instruction_register are declared as

   subtype index_mode is integer range 0 to 3;
   variable instruction_register : integer range 0 to 2**16 - 1;

then we can write a case statement that uses a value of this type:

   case index_mode'((instruction_register / 2**12) rem 2**2) is
    when 0 =>
      index_value := 0;
    when 1 =>
      index_value := accumulator_A;
    when 2 =>
      index_value := accumulator_B;
    when 3 =>
      index_value := index_register;
   end case;

Notice that in this example, we use a qualified expression in the selector expression. If we had omitted this, the result of the expression would have been integer, and we would have had to include alternatives to cover all possible integer values. The type qualification avoids this need by limiting the possible values of the expression.

Another rule to remember is that the type of each choice must be the same as the type resulting from the selector expression. Thus in the above example, it is illegal to include an alternative such as

   when 'a' => ...   -- illegal!

since the choice listed cannot be an integer. Such a choice does not make sense, since it can never match a value of type integer.

We can include more than one choice in each alternative by writing the choices separated by the “|” symbol. For example, if the type opcodes is declared as

   type opcodes is
    (nop, add, subtract, load, store, jump, jumpsub, branch, halt);

we could write an alternative including three of these values as choices:

   when load | add | subtract =>
    operand := memory_operand;

If we have a number of alternatives in a case statement and we want to include an alternative to handle all possible values of the selector expression not mentioned in previous alternatives, we can use the special choice others. For example, if the variable opcode is a variable of type opcodes, declared above, we can write

   case opcode is
    when load | add | subtract =>
      operand := memory_operand;
    when store | jump | jumpsub | branch =>
      operand := address_operand;
    when others =>
      operand := 0;
   end case;

In this example, if the value of opcode is anything other than the choices listed in the first and second alternatives, the last alternative is selected. There may only be one alternative that uses the others choice, and if it is included, it must be the last alternative in the case statement. An alternative that includes the others choice may not include any other choices. Note that, if all of the possible values of the selector expression are covered by previous choices, we may still include the others choice, but it can never be matched.

The remaining form of choice that we have not yet mentioned is a discrete range, specified by these simplified syntax rules:

   discrete_range ⇐
      discrete_subtype_indication
      | simple_expression ( to | downto ) simple_expression
   subtype_indication ⇐
      type_mark
          [ range simple_expression ( to | downto ) simple_expression ]

These forms allow us to specify a range of values in a case statement alternative. If the value of the selector expression matches any of the values in the range, the statements in the alternative are executed. The simplest way to specify a discrete range is just to write the left and right bounds of the range, separated by a direction keyword. For example, the case statement above could be rewritten as

   case opcode is
    when add to load =>
      operand := memory_operand;
    when branch downto store =>
      operand := address_operand;
    when others =>
      operand := 0;
   end case;

Another way of specifying a discrete range is to use the name of a discrete type, and possibly a range constraint to narrow down the values to a subset of the type. For example, if we declare a subtype of opcodes as

   subtype control_transfer_opcodes is opcodes range jump to branch;

we can rewrite the second alternative as

   when control_transfer_opcodes | store =>
    operand := address_operand;

Note that we may only use a discrete range as a choice if the selector expression is of a discrete type. We may not use a discrete range if the selector expression is of an array type, such as a bit-vector type. If we specify a range by writing the bounds and a direction, the direction has no significance except to identify the contents of the range.

An important point to note about the choices in a case statement is that they must all be written using locally static values. This means that the values of the choices must be determined during the analysis phase of design processing. All of the above examples satisfy this requirement. To give an example of a case statement that fails this requirement, suppose we have an integer variable N, declared as

   variable N : integer := 1;

If we wrote the case statement

   case expression is         -- example of an illegal case statement
    when N | N+1 => ...
    when N+2 to N+5 => ...
    when others => ...
   end case;

the values of the choices depend on the value of the variable N. Since this might change during the course of execution, these choices are not locally static. Hence the case statement as written is illegal. On the other hand, if we had declared C to be a constant integer, for example with the declaration

   constant C : integer := 1;

then we could legally write the case statement

   case expression is
    when C | C+1 => ...
    when C+2 to C+5 => ...
    when others => ...
   end case;

This is legal, since we can determine, by analyzing the model, that the first alternative includes choices 1 and 2, the second includes numbers between 3 and 6 and the third covers all other possible values of the expression.

The previous examples all show only one statement in each alternative. As with the if statement, we can write an arbitrary number of sequential statements of any kind in each alternative. This includes writing nested case statements, if statements or any other form of sequential statements in the alternatives.

Although the preceding rules governing case statements may seem complex, in practice there are just a few things to remember, namely:

  • all possible values of the selector expression must be covered by one and only one choice,

  • the values in the choices must be locally static and

  • if the others choice is used it must be in the last alternative and must be the only choice in that alternative.

Example 3.2. A four-input multiplexer

We can write a behavioral model of a multiplexer with a select input sel; four data inputs d0, d1, d2 and d3; and a data output z. The data inputs and outputs are of the IEEE standard-logic type, and the select input is of type sel_range, which we assume to be declared elsewhere as

   type sel_range is range 0 to 3;

We described in Section 2.2 how we define a type in a package for use in an entity declaration. The entity declaration defining the ports and a behavioral architecture body are:

   library ieee;  use ieee.std_logic_1164.all;

   entity mux4 is
    port ( sel : in sel_range;
           d0, d1, d2, d3 : in std_ulogic;
           z : out std_ulogic );
   end entity mux4;
   --------------------------------------------------
   architecture demo of mux4 is
   begin
    out_select : process (sel, d0, d1, d2, d3) is
    begin
      case sel is
        when 0 =>
          z <= d0;
        when 1 =>
          z <= d1;
        when 2 =>
          z <= d2;
        when 3 =>
          z <= d3;
      end case;
    end process out_select;
   end architecture demo;

The architecture body contains just a process declaration. Since the output of the multiplexer must change if any of the data or select inputs change, the process must be sensitive to all of the inputs. It makes use of a case statement to select which of the data inputs is to be assigned to the data output.

VHDL-87

Case statements may not be labeled in VHDL-87.

Selected Variable Assignments

Just as there is a shorthand notation for an if statement containing variable assignments, there is also a shorthand for a case statement containing variable assignments. It is called a selected variable assignment, and the syntax rule is

   selected_variable_assignment ⇐
      [ label : ]
      with expression select
          name := { expression when choices , }
                  expression when choices ;

The first expression is the selector expression, and its value is compared with the choices to determine which expression value to assign to the named variable.

As an example, we could rewrite the case statement on page 69 as:

   with func select
    result := operand1            when pass1,
              operand2            when pass2,
              operand1 + operand2 when add,
              operand1 - operand2 when subtract;

As with the conditional shorthand, if we need to perform more than one assignment for each alternative, or if the actions required are not assignments, we would still use a case statement.

VHDL-87, -93, and -2002

These versions of VHDL do not provide the selected variable assignment shorthand notation. We must write a case statement with a separate variable assignment for each alternative.

Null Statements

Sometimes when writing models we need to state that when some condition arises, no action is to be performed. This need often arises when we use case statements, since we must include an alternative for every possible value of the selector expression. Rather than just leaving the statement part of an alternative blank, we can use a null statement to state explicitly that nothing is to be done. The syntax rule for the null statement is simply

   null_statement ⇐ [ label : ] null ;

The optional label serves to identify the statement. We discuss labeled statements in Chapter 20. A simple, unlabeled null statement is

   null;

An example of its use in a case statement is

   case opcode is
    when add =>
      Acc := Acc + operand;
    when subtract =>
      Acc := Acc - operand;
    when nop =>
      null;
   end case;

We can use a null statement in any place where a sequential statement is required, not just in a case statement alternative. A null statement may be used during the development phase of model writing. If we know, for example, that we will need an entity as part of a system, but we are not yet in a position to write a detailed model for it, we can write a behavioral model that does nothing. Such a model just includes a process with a null statement in its body:

   control_section : process ( sensitivity_list ) is
   begin
    null;
   end process control_section;

Note that the process must include the sensitivity list, for reasons that are explained in Chapter 5.

VHDL-87

Null statements may not be labeled in VHDL-87.

Loop Statements

Often we need to write a sequence of statements that is to be repeatedly executed. We use a loop statement to express this behavior. There are several different forms of loop statements in VHDL; the simplest is a loop that repeats a sequence of statements indefinitely, often called an infinite loop. The syntax rule for this kind of loop is

   loop_statement ⇐
      [ loop_label : ]
      loop
          { sequential_statement }
      end loop [ loop_label ] ;

In most computer programming languages, an infinite loop is not desirable, since it means that the program never terminates. However, when we are modeling digital systems, an infinite loop can be useful, since many hardware devices repeatedly perform the same function until we turn off the power. Typically a model for such a system includes a loop statement in a process body; the loop, in turn, contains a wait statement.

Example 3.3. A modulo-16 counter

The following is a model for a counter that starts from zero and increments on each clock transition from ‘0’ to ‘1’. When the counter reaches 15, it wraps back to zero on the next clock transition. The architecture body for the counter contains a process that first initializes the count output to zero, then repeatedly waits for a clock transition before incrementing the count value.

   entity counter is
    port ( clk : in bit;  count : out natural );
   end entity counter;
   --------------------------------------------------
   architecture behavior of counter is
   begin
    incrementer : process is
      variable count_value : natural := 0;
    begin
      count <= count_value;
      loop
        wait until clk;
        count_value := (count_value + 1) mod 16;
        count <= count_value;
      end loop;
    end process incrementer;
   end architecture behavior;

The wait statement in this example causes the process to suspend in the middle of the loop. When the clk signal changes from ‘0’ to ‘1’, the process resumes and updates the count value and the count output. The loop is then repeated starting with the wait statement, so the process suspends again.

Another point to note in passing is that the process statement does not include a sensitivity list. This is because it includes a wait statement. A process may contain either a sensitivity list or wait statements, but not both. We will return to this in detail in Chapter 5.

Exit Statements

In the previous example, the loop repeatedly executes the enclosed statements, with no way of stopping. Usually we need to exit the loop when some condition arises. We can use an exit statement to exit a loop. The syntax rule is

   exit_statement ⇐
      [ label : ] exit [ loop_label ] [ when condition ] ;

The optional label at the start of the exit statement serves to identify the statement. We discuss labeled statements in Chapter 20. The simplest form of exit statement is just

   exit;

When this statement is executed, any remaining statements in the loop are skipped, and control is transferred to the statement after the end loop keywords. So in a loop we can write

   if condition then
    exit;
   end if;

Since this is perhaps the most common use of the exit statement, VHDL provides a shorthand way of writing it, using the when clause. We use an exit statement with the when clause in a loop of the form

   loop
    ...
    exit when condition;
    ...
   end loop;
   ...          -- control transferred to here
            -- when condition becomes true within the loop

Example 3.4. A modulo-16 counter with reset

We now revise the counter model from Example 3.3 to include a reset input that, when ‘1’, causes the count output to be reset to zero. The output stays at zero as long as the reset input is ‘1’ and resumes counting on the next clock transition after reset changes to ‘0’. The revised entity declaration includes the new input port.

   entity counter is
    port ( clk, reset : in bit;  count : out natural );
   end entity counter;
   --------------------------------------------------
   architecture behavior of counter is
   begin
    incrementer : process is
      variable count_value : natural := 0;
    begin
      count <= count_value;
      loop
        loop
          wait until clk or reset;
          exit when reset;
          count_value := (count_value + 1) mod 16;
          count <= count_value;
        end loop;
        -- at this point, reset = '1'
        count_value := 0;
        count <= count_value;
        wait until not reset;
      end loop;
    end process incrementer;
   end architecture behavior;

The architecture body is revised by nesting the loop inside another loop statement and adding the reset signal to the original wait statement. The inner loop performs the same function as before, except that when reset changes to ‘1’, the process is resumed, and the exit statement causes the inner loop to be terminated. Control is transferred to the statement just after the end of the inner loop. As the comment indicates, we know that this point can only be reached when reset is ‘1’. The count value and count outputs are reset, and the process then waits for reset to return to ‘0’. While it is suspended at this point, any changes on the clock input are ignored. When reset changes to ‘0’, the process resumes, and the outer loop repeats.

This example also illustrates another important point. When we have nested loop statements, with an exit statement inside the inner loop, the exit statement causes control to be transferred out of the inner loop only, not the outer loop. By default, an exit statement transfers control out of the immediately enclosing loop.

In some cases, we may wish to transfer control out of an inner loop and also a containing loop. We can do this by labeling the outer loop and using the label in the exit statement. We can write

   loop_name : loop
    ...
    exit loop_name;
    ...
   end loop loop_name;

This labels the loop with the name loop_name, so that we can indicate which loop to exit in the exit statement. The loop label can be any valid identifier. The exit statement referring to this label can be located within nested loop statements.

To illustrate how loops can be nested, labeled and exited, let us consider the following statements:

   outer : loop
    ...
    inner : loop
      ...
      exit outer when condition_1;  -- exit 1
      ...
      exit when condition_2;        -- exit 2
      ...
    end loop inner;
    ...                             -- target A
    exit outer when condition_3;    -- exit 3
     ...
   end loop outer;
   ...                              -- target B

This example contains two loop statements, one labeled inner nested inside another labeled outer. The first exit statement, tagged with the comment exit 1, transfers control to the statement tagged target B if its condition is true. The second exit statement, tagged exit 2, transfers control to target A. Since it does not refer to a label, it only exits the immediately enclosing loop statement, namely, loop inner. Finally, the exit statement tagged exit 3 transfers control to target B.

VHDL-87, -93, and -2002

Since these versions of VHDL do not perform implicit conversion of conditions to boolean, conditions in exit statements must yield boolean results without conversion. Hence, we must write the exit statement in Example 3.4 as:

   exit when reset = '1';

VHDL-87

Exit statements may not be labeled in VHDL-87.

Next Statements

Another kind of statement that we can use to control the execution of loops is the next statement. When this statement is executed, the current iteration of the loop is completed without executing any further statements, and the next iteration is begun. The syntax rule is

   next_statement ⇐
      [ label : ] next [ loop_label ] [ when condition ] ;

The optional label at the start of the next statement serves to identify the statement. We discuss labeled statements in Chapter 20. A next statement is very similar in form to an exit statement, the difference being the keyword next instead of exit. The simplest form of next statement is

   next;

which starts the next iteration of the immediately enclosing loop. We can also include a condition to test before completing the iteration:

   next when condition;

and we can include a loop label to indicate for which loop to complete the iteration:

   next loop-label;

or

   next loop-label when condition;

A next statement that exits the immediately enclosing loop can be easily rewritten as an equivalent loop with an if statement replacing the next statement. For example, the following two loops are equivalent:

   loop                            loop
    statement-1;                    statement-1;
    next when condition;            if not condition then
    statement-2;                      statement-2;
   end loop;                        end if;
                                 end loop;

However, nested labeled loops that contain next statements referring to outer loops cannot be so easily rewritten. As a matter of style, if we find ourselves about to write such a collection of loops and next statements, it’s probably time to think more carefully about what we are trying to express. If we check the logic of the model, we may be able to find a simpler formulation of loop statements. Complicated loop/next structures can be confusing, making the model hard to read and understand.

VHDL-87, -93, and -2002

Since these versions of VHDL do not perform implicit conversion of conditions to boolean, conditions in next statements must yield boolean results without conversion.

VHDL-87

Next statements may not be labeled in VHDL-87.

While Loops

We can augment the basic loop statement introduced previously to form a while loop, which tests a condition before each iteration. If the condition is true, iteration proceeds. If it is false, the loop is terminated. The syntax rule for a while loop is

   loop_statement ⇐
      [ loop_label : ]
      while condition loop
          { sequential_statement }
      end loop [ loop_label ] ;

The only difference between this form and the basic loop statement is that we have added the keyword while and the condition before the loop keyword. All of the things we said about the basic loop statement also apply to a while loop. We can write any sequential statements in the body of the loop, including exit and next statements, and we can label the loop by writing the label before the while keyword.

There are three important points to note about while loops. The first point is that the condition is tested before each iteration of the loop, including the first iteration. This means that if the condition is false before we start the loop, it is terminated immediately, with no iterations being executed. For example, given the while loop

   while index > 0 loop
    ...      -- statement A: do something with index
   end loop;
   ...        -- statement B

if we can demonstrate that index is not greater than zero before the loop is started, then we know that the statements inside the loop will not be executed, and control will be transferred straight to statement B.

The second point is that in the absence of exit statements within a while loop, the loop terminates only when the condition becomes false. Thus, we know that the negation of the condition must hold when control reaches the statement after the loop. Similarly, in the absence of next statements within a while loop, the loop performs an iteration only when the condition is true. Thus, we know that the condition holds when we start the statements in the loop body. In the above example, we know that index must be greater then zero when we execute the statement tagged statement A, and also that index must be less than or equal to zero when we reach statement B. This knowledge can help us reason about the correctness of the model we are writing.

The third point is that when we write the statements inside the body of a while loop, we must make sure that the condition will eventually become false, or that an exit statement will eventually exit the loop. Otherwise the while loop will never terminate. Presumably, if we had intended to write an infinite loop, we would have used a simple loop statement.

Example 3.5. A cosine module

We can develop a model for an entity cos that might be used as part of a specialized signal processing system. The entity has one input, theta, which is a real number representing an angle in radians, and one output, result, representing the cosine function of the value of theta. We can use the relation

A cosine module

by adding successive terms of the series until the terms become smaller than one millionth of the result. The entity and architecture body declarations are:

   entity cos is
    port ( theta : in real;  result : out real );
   end entity cos;
   --------------------------------------------------
   architecture series of cos is
   begin
    summation : process (theta) is
      variable sum, term : real;
      variable n : natural;
    begin
      sum := 1.0;
      term := 1.0;
      n := 0;
      while abs term > abs (sum / 1.0E6) loop
        n := n + 2;
        term := (-term) * theta**2 / real(((n-1) * n));
        sum := sum + term;
      end loop;
      result <= sum;
    end process summation;
   end architecture series;

The architecture body consists of a process that is sensitive to changes in the input signal theta. Initially, the variables sum and term are set to 1.0, representing the first term in the series. The variable n starts at 0 for the first term. The cosine function is computed using a while loop that increments n by two and uses it to calculate the next term based on the previous term. Iteration proceeds as long as the last term computed is larger in magnitude than one millionth of the sum. When the last term falls below this threshold, the while loop is terminated. We can determine that the loop will terminate, since the values of successive terms in the series get progressively smaller. This is because the factorial function grows at a greater rate than the exponential function.

VHDL-87, -93, and -2002

Since these versions of VHDL do not perform implicit conversion of conditions to boolean, conditions in while loops must yield boolean results without conversion.

For Loops

Another way we can augment the basic loop statement is the for loop. A for loop includes a specification of how many times the body of the loop is to be executed. The syntax rule for a for loop is

   loop_statement ⇐
      [ loop_label : ]
      for identifier in discrete_range loop
          { sequential_statement }
      end loop [ loop_label ] ;

We saw on page 71 that a discrete range can be of the form

   simple_expression ( to | downto | simple_expression

representing all the values between the left and right bounds, inclusive. The identifier is called the loop parameter, and for each iteration of the loop, it takes on successive values of the discrete range, starting from the left element. For example, in this for loop:

   for count_value in 0 to 127 loop
    count_out <= count_value;
    wait for 5 ns;
   end loop;

the identifier count_value takes on the values 0, 1, 2 and so on, and for each value, the assignment and wait statements are executed. Thus the signal count_out will be assigned values 0, 1, 2 and so on, up to 127, at 5 ns intervals.

We also saw that a discrete range can be specified using a discrete type or subtype name, possibly further constrained to a subset of values by a range constraint. For example, if we have the enumeration type

   type controller_state is (initial, idle, active, error);

we can write a for loop that iterates over each of the values in the type:

   for state in controller_state loop
    ...
   end loop;

Within the sequence of statements in the for loop body, the loop parameter is a constant whose type is the base type of the discrete range. This means we can use its value by including it in an expression, but we cannot make assignments to it. Unlike other constants, we do not need to declare it. Instead, the loop parameter is implicitly declared over the for loop. It only exists when the loop is executing, and not before or after it. For example, the following process statement shows how not to use the loop parameter:

   erroneous : process is
    variable i, j : integer;
   begin
    i := loop_param;                -- error!
    for loop_param in 1 to 10 loop
      loop_param := 5;              -- error!
    end loop;
    j := loop_param;                -- error!
   end process erroneous;

The assignments to i and j are illegal since the loop parameter is defined neither before nor after the loop. The assignment within the loop body is illegal because loop_param is a constant and thus may not be modified.

A consequence of the way the loop parameter is defined is that it hides any object of the same name defined outside the loop. For example, in this process:

   hiding_example : process is
    variable a, b : integer;
   begin
    a := 10;
    for a in 0 to 7 loop
      b := a;
    end loop;
    -- a = 10, and b = 7
    ...
   end process hiding_example;

the variable a is initially assigned the value 10, and then the for loop is executed, creating a loop parameter also called a. Within the loop, the assignment to b uses the loop parameter, so the final value of b after the last iteration is 7. After the loop, the loop parameter no longer exists, so if we use the name a, we are referring to the variable object, whose value is still 10.

As we mentioned above, the for loop iterates with the loop parameter assuming successive values from the discrete range starting from the leftmost value. An important point to note is that if we specify a null range, the for loop body does not execute at all. A null range can arise if we specify an ascending range with the left bound greater than the right bound, or a descending range with the left bound less than the right bound. For example, the for loop

   for i in 10 to 1 loop
    ...
   end loop;

completes immediately, without executing the enclosed statements. If we really want the loop to iterate with i taking values 10, 9, 8 and so on, we should write

   for i in 10 downto 1 loop
    ...
   end loop;

One final thing to note about for loops is that, like basic loop statements, they can enclose arbitrary sequential statements, including next and exit statements, and we can label a for loop by writing the label before the for keyword.

Example 3.6. A revised cosine module

We now rewrite the cosine model in Example 3.5 to calculate the result by summing the first 10 terms of the series. The entity declaration is unchanged. The revised architecture body, shown below, consists of a process that uses a for loop instead of a while loop. As before, the variables sum and term are set to 1.0, representing the first term in the series. The variable n is replaced by the for loop parameter. The loop iterates nine times, calculating the remaining nine terms of the series.

   architecture fixed_length_series of cos is
   begin
    summation : process (theta) is
      variable sum, term : real;
    begin
      sum := 1.0;
      term := 1.0;
      for n in 1 to 9 loop
        term := (-term) * theta**2 / real(((2*n-1) * 2*n));
        sum := sum + term;
      end loop;
      result <= sum;
    end process summation;
   end architecture fixed_length_series;

Summary of Loop Statements

The preceding sections describe the various forms of loop statements in detail. It is worth summarizing this information in one place, to show the few basic points to remember. First, the syntax rule for all loop statements is

   loop_statement ⇐
      [ loop_label : ]
      [ while condition | for identifier in discrete_range ] loop
          { sequential_statement }
      end loop [ loop_label ] ;

Second, in the absence of exit and next statements, the while loop iterates as long as the condition is true, and the for loop iterates with the loop parameter assuming successive values from the discrete range. If the condition in a while loop is initially false, or if the discrete range in a for loop is a null range, then no iterations occur.

Third, the loop parameter in a for loop cannot be explicitly declared, and it is a constant within the loop body. It also shadows any other object of the same name declared outside the loop.

Finally, an exit statement can be used to terminate any loop, and a next statement can be used to complete the current iteration and commence the next iteration. These statements can refer to loop labels to terminate or complete iteration for an outer level of a nested set of loops.

Assertion and Report Statements

One of the reasons for writing models of computer systems is to verify that a design functions correctly. We can partially test a model by applying sample inputs and checking that the outputs meet our expectations. If they do not, we are then faced with the task of determining what went wrong inside the design. This task can be made easier using assertion statements that check that expected conditions are met within the model. An assertion statement is a sequential statement, so it can be included anywhere in a process body. The full syntax rule for an assertion statement is

   assertion_statement ⇐
      [ label : ] assert condition
              [ report expression ] [ severity expression ] ;

The optional label allows us to identify the assertion statement. We will discuss labeled statements in Chapter 20. The simplest form of assertion statement just includes the keyword assert followed by a condition that we expect to be true when the assertion statement is executed. If the condition is not met, we say that an assertion violation has occurred. If an assertion violation arises during simulation of a model, the simulator reports the fact. During synthesis, the condition in an assertion statement may be interpreted as a condition that the synthesizer may assume to be true. During formal verification, the condition may be interpreted as a condition to be proven by the verifier. For example, if we write

   assert initial_value <= max_value;

and initial_value is larger than max_value when the statement is executed during simulation, the simulator will let us know. During synthesis, the synthesizer may assume that initial_value <= max_value and optimize the circuit based on that information. During formal verification, the verifier may attempt to prove initial_value <= max_value for all possible input stimuli and execution paths leading to the assertion statement.

If we have a number of assertion statements throughout a model, it is useful to know which assertion is violated. We can get the simulator to provide extra information by including a report clause in an assertion statement, for example:

   assert initial_value <= max_value
    report "initial value too large";

The string that we provide is used to form part of the assertion violation message. We can write any expression in the report clause provided it yields a string value, for example:

   assert current_character >= '0' and current_character <= '9'
    report "Input number " & input_string & " contains a non-digit";

Here the message is derived by concatenating three string values together. We can use the to_string operation to get a string representation of a value to include in a message, for example:

   assert initial_value <= max_value
    report "initial value " & to_string(initial_value)
           & " too large";

In Section 2.2, we mentioned a predefined enumeration type severity_level, defined as

   type severity_level is (note, warning, error, failure);

We can include a value of this type in a severity clause of an assertion statement. This value indicates the degree to which the violation of the assertion affects operation of the model. The value note can be used to pass informative messages out from a simulation, for example:

   assert free_memory >= low_water_limit
    report "low on memory, about to start garbage collect"
    severity note;

The severity level warning can be used if an unusual situation arises in which the model can continue to execute, but may produce unusual results, for example:

   assert packet_length /= 0
    report "empty network packet received"
    severity warning;

We can use the severity level error to indicate that something has definitely gone wrong and that corrective action should be taken, for example:

   assert clock_pulse_width >= min_clock_width
    severity error;

Finally, the value failure can be used if we detect an inconsistency that should never arise, for example:

   assert (last_position - first_position + 1) = number_of_entries
    report "inconsistency in buffer model"
    severity failure;

We have seen that we can write an assertion statement with either a report clause or a severity clause, or both. If both are present, the syntax rule shows us that the report clause must come first. If we omit the report clause, the default string in the error message is “Assertion violation.” If we omit the severity clause, the default value is error. The severity value is usually used by a simulator to determine whether or not to continue execution after an assertion violation. Most simulators allow the user to specify a severity threshold, beyond which execution is stopped. The VHDL standard recommends that, in the absence of such a specification, simulation continue for assertion violations with severity error or less.

Usually, failure of an assertion means either that the entity is being used incorrectly as part of a larger design or that the model for the entity has been incorrectly written. We illustrate both cases.

Example 3.7. A set/reset flipflop including a check for correct usage

A set/reset (SR) flipflop has two inputs, S and R, and an output Q. When S is ‘1’, the output is set to ‘1’, and when R is ‘1’, the output is reset to ‘0’. However, S and R may not both be ‘1’ at the same time. If they are, the output value is not specified. A behavioral model for an SR flipflop that includes a check for this illegal condition is:

   entity SR_flipflop is
    port ( S, R : in bit;  Q : out bit );
   end entity SR_flipflop;
   --------------------------------------------------
   architecture checking of SR_flipflop is
   begin
      set_reset : process (S, R) is
      begin
        assert S nand R;
        if S then
          Q <= '1';
        end if;
        if R then
          Q <= '0';
        end if;
      end process set_reset;
   end architecture checking;

The architecture body contains a process sensitive to the S and R inputs. Within the process body we write an assertion statement that requires that S and R not both be ‘1’. If both are ‘1’, the assertion is violated, so the simulator writes an “Assertion violation” message with severity error. If execution continues after the violated assertion, the value ‘1’ will first be assigned to Q, followed by the value ‘0’. The resulting value is ‘0’. This is allowed, since the state of Q was not specified for this illegal condition, so we are at liberty to choose any value. If the assertion is not violated, then at most one of the following if statements is executed, correctly modeling the behavior of the SR flipflop.

Example 3.8. Sanity check on calculation of the maximum value

To illustrate the use of an assertion statement as a “sanity check,” let us look at a model for an entity that has three integer inputs, a, b and c, and produces an integer output z that is the largest of its inputs.

   entity max3 is
    port ( a, b, c : in integer;  z : out integer );
   end entity max3;
   --------------------------------------------------
   architecture check_error of max3 is
   begin
      maximizer : process (a, b, c)
        variable result : integer;
      begin
        if a > b then
          if a > c then
            result := a;
          else
            result := a;  -- Oops!  Should be: result := c;
          end if;
        elsif  b > c then
          result := b;
        else
          result := c;
        end if;
        assert result >= a and result >= b and result >= c
          report "inconsistent result for maximum:"
                 & to_string(result)
          severity failure;
        z <= result;
      end process maximizer;
   end architecture check_error;

The architecture body is written using a process containing nested if statements. For this example we have introduced an “accidental” error into the model. If we simulate this model and put the values a = 7, b = 3 and c = 9 on the ports of this entity, we expect that the value of result, and hence the output port, is 9. The assertion states that the value of result must be greater than or equal to all of the inputs. However, our coding error causes the value 7 to be assigned to result, and so the assertion is violated. This violation causes us to examine our model more closely, and correct the error.

Another important use for assertion statements is in checking timing constraints that apply to a model. For example, most clocked devices require that the clock pulse be longer than some minimum duration. We can use the predefined primary now in an expression to calculate durations. We return to now in a later chapter. Suffice it to say that it yields the current simulation time when it is evaluated.

Example 3.9. An edge-triggered register with timing check

An edge-triggered register has a data input and a data output of type real and a clock input of type bit. When the clock changes from ‘0’ to ‘1’, the data input is sampled, stored and transmitted through to the output. Let us suppose that the clock input must remain at ‘1’ for at least 5 ns. The following is a model for this register, including a check for legal clock pulse width.

   entity edge_triggered_register is
    port ( clock : in bit;
           d_in : in real;  d_out : out real );
   end entity edge_triggered_register;
   --------------------------------------------------
   architecture check_timing of edge_triggered_register is
   begin
    store_and_check : process (clock) is
      variable stored_value : real;
      variable pulse_start : time;
    begin
      if rising_edge(clock) then
        pulse_start := now;
        stored_value := d_in;
        d_out <= stored_value;
      else
        assert now = 0 ns or (now - pulse_start) >= 5 ns
          report "clock pulse too short:"
                 & to_string(now - pulse_start);
      end if;
    end process store_and_check;
   end architecture check_timing;

The architecture body contains a process that is sensitive to changes on the clock input. When the clock changes from ‘0’ to ‘1’, the input is stored, and the current simulation time is recorded in the variable pulse_start. Otherwise, when the clock changes from ‘1’ to ‘0’, the difference between pulse_start and the current simulation time is checked by the assertion statement.

VHDL-87

Assertion statements may not be labeled in VHDL-87.

VHDL also provides us with a report statement, which is similar to an assertion statement. The syntax rule for the report statement shows this similarity:

   report_statement ⇐
      [ label : ] report expression [ severity expression ] ;

The differences are that there is no condition, and if the severity level is not specified, the default is note. Indeed, the report statement can be thought of as an assertion statement in which the condition is the value false and the severity is note, hence it always produces the message. One way in which the report statement is useful is as a means of including “trace writes” in a model as an aid to debugging.

Example 3.10. Trace messages using a report statement

Suppose we are writing a complex model and we are not sure that we have got the logic quite right. We can use report statements to get the processes in the model to write out messages, so that we can see when they are activated and what they are doing. An example process is

   transmit_element : process (transmit_data) is
   ...       -- variable declarations
   begin
    report "transmit_element: data = "
           & to_string(transmit_data);
    ...
   end process transmit_element;

Both assertion statements and report statements allow inclusion of a message string to provide useful information. In some cases, the information we wish to provide may be extensive and not fit entirely on a single line when displayed. We can include the line-feed character in the message string to break the message over multiple lines. A line-feed is represented by the identifier LF of type character. For example:

   assert data = expected_data
    report "%%%ERROR data value miscompare." & LF &
            " Actual value = " & to_string(data) & LF &
            " Expected value = " & to_string(expdata) & LF &
            " at time: " & to_string(now) );

The message produced when this assertion is violated consists of four lines of text. A VHDL tool interprets the line feed characters using the appropriate convention for the host operating system. For example, if a Unix-based system were to write the message to a file, it would just include the line-feed characters. A Windows-based system, on the other hand, would write a carriage-return/line-feed pair for every line-feed in the message.

VHDL-87, -93, and -2002

These versions of VHDL do not make any recommendation about continuing simulation based on the severity of an assertion violation from an assertion statement or report statement. Different simulators take different approaches, which can make it difficult to write portable models with consistent simulation behavior on different tools.

These versions also do not necessarily interpret line-feed characters in message strings as denoting line breaks. Interpretation of line feeds is implementation defined.

VHDL-87

Report statements are not provided in VHDL-87. We achieve the same effect by writing an assertion statement with the condition false and a severity level of note. For example, the VHDL-93 or VHDL-2002 report statement

   report "Initialization complete";

can be written in VHDL-87 as

   assert false
    report "Initialization complete" severity note;

Exercises

1.

[Exercises3.1] Write an if statement that sets a variable odd to ‘1’ if an integer n is odd, or to ‘0’ if it is even. Rewrite your if statement as a conditional variable assignment.

2.

[Exercises3.1] Write an if statement that, given the year of today’s date in the variable year, sets the variable days_in_February to the number of days in February. A year is a leap year if it is divisible by four, except for years that are divisible by 100. However, years that are divisible by 400 are leap years. February has 29 days in a leap year and 28 days otherwise. Rewrite your if statement as a conditional variable assignment.

3.

[Exercises3.2] Write a case statement that strips the strength information from a standard-logic variable x. If x is ‘0’ or ‘L’, set it to ‘0’. If x is ‘1’ or ‘H’, set it to ‘1’. If x is ‘X’, ‘W’, ‘Z’, ‘U’ or ‘–’, set it to ‘X’. (This is the conversion performed by the standard-logic function to_X01.) Rewrite your case statement as a selected variable assignment.

4.

[Exercises3.2] Write a case statement that sets an integer variable character_class to 1 if the character variable ch contains a letter, to 2 if it contains a digit, to 3 if it contains some other printable character or to 4 if it contains a non-printable character. Note that the VHDL character set contains accented letters, as shown in Section 2.2.5 on page 44. Rewrite your case statement as a selected variable assignment.

5.

[Exercises3.4] Write a loop statement that samples a bit input d when a clock input clk changes to ‘1’. So long as d is ‘0’, the loop continues executing. When d is ‘1’, the loop exits.

6.

[Exercises3.4] Write a while loop that calculates the exponential function of x to an accuracy of one part in 104 by summing terms of the following series:

Exercises

7.

[Exercises3.4] Write a for loop that calculates the exponential function of x by summing the first eight terms of the series in Exercise 6.

8.

[Exercises3.5] Write an assertion statement that expresses the requirement that a flipflop’s two outputs, q and q_n, of type std_ulogic, are complementary.

9.

[Exercises3.5] We can use report statements in VHDL to achieve the same effect as using “trace writes” in software programming languages, to report a message when part of the model is executed. Insert a report statement in the model of Example 3.4 to cause a trace message when the counter is reset.

10.

[Exercises3.1] Develop a behavioral model for a limiter with three integer inputs, data_in, lower and upper; an integer output, data_out; and a bit output, out_of_limits. The data_out output follows data_in so long as it is between lower and upper. If data_in is less than lower, data_out is limited to lower. If data_in is greater than upper, data_out is limited to upper. The out_of_limit output indicates when data_out is limited.

11.

[Exercises3.2] Develop a model for a floating-point arithmetic unit with data inputs x and y, data output z and function code inputs f1 and f0 of type bit. Function codes f1 = ‘0’ and f0 = ‘0’ produce addition; f1 = ‘0’ and f0 = ‘1’ produce subtraction of y from x; f1 = ‘1’ and f0 = ‘0’ produce multiplication; and f1 = ‘1’ and f0 = ‘1’ produce division of x by y.

12.

[Exercises3.4] Write a model for a counter with an output port of type natural, initially set to 15. When the clk input changes to ‘1’, the counter decrements by one. After counting down to zero, the counter wraps back to 15 on the next clock edge.

13.

[Exercises3.4] Modify the counter of Exercise 12 to include an asynchronous load input and a data input. When the load input is ‘1’, the counter is preset to the data input value. When the load input changes back to ‘0’, the counter continues counting down from the preset value.

14.

[Exercises3.4] Develop a model of an averaging module that calculates the average of batches of 16 real numbers. The module has clock and data inputs and a data output. The module accepts the next input number when the clock changes to ‘1’. After 16 numbers have been accepted, the module places their average on the output port, then repeats the process for the next batch.

15.

[Exercises3.5] Write a model that causes assertion violations with different severity levels. Experiment with your simulator to determine its behavior when an assertion violation occurs. See if you can specify a severity threshold above which it stops execution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.133.206