Understanding Concurrent Statements

(from VHDL Made Easy!)


In Chapter 2, A First Look At VHDL, we covered the three primary design description styles (behavior, dataflow and structure) supported by VHDL. We also discussed the difference between writing VHDL statements that are concurrent (written within the body of an architecture declaration) and those that are sequential (written within a VHDL process statement, function or procedure). We looked at examples of all three description styles and also demonstrated, by example, that there is not a direct relationship between (a) concurrent and sequential statements in VHDL, and (b) combinational and sequential (registered) logic.

In this chapter, we will examine more closely the concept of concurrency as it is implemented in VHDL and VHDL simulators. We will also explore some of the concurrent language features of VHDL in more detail and learn how combinational and registered logic can be described using these features. In addition, we will look briefly at how timing delays are annotated to concurrent assignments in VHDL, so you will have a better understanding of how simulation models are constructed.

The Concurrent Area

In VHDL, there is only one place where concurrent statements can be entered. This place, the concurrent area, is found between the begin and end statements of an architecture declaration. The following VHDL sample shows where the concurrent area of a VHDL architecture is located:
architecture arch1 of my_circuit is
    signal Reset, DivClk: std_ulogic;
    constant MaxCount: std_logic_vector(15 downto 0) := "10001111";
    component count port (Clk, Rst: in std_ulogic;
                          Q: out std_ulogic_vector(15 downto 0));

-- The concurrent area starts with the begin statement
-- in the next line.
begin

    Reset <= '1' when Qout  = MaxCount else '0';

    CNT1: count port map(GClk, Reset, DivClk);
    
    Control: process(DivClk)
    begin
        . . .
    end process     . . .
end arch1;
-- The concurrent area ends with the "end arch1" statement
-- in the previous line.
All statements within the concurrent area are considered to be parallel in their execution and of equal priority and importance. Processes (described in more detail in Chapter 6, Understanding Sequential Statements) also obey this rule, executing in parallel with other assignments and processes appearing in the concurrent area.

There is no order dependency to statements in the concurrent area, so the architecture declaration:

architecture arch1 of my_circuit is
    signal A, B, C: std_ulogic_vector(7 downto 0);
    constant Init: std_ulogic_vector(7 downto 0) := "01010101";
begin
    A <= B and C;
    B <= Init when Select = '1' else C;
    C <= A and B;
end arch1;
is exactly equivalent to:
architecture arch2 of my_circuit is
    signal A, B, C: std_ulogic_vector(7 downto 0);
    constant Init: std_ulogic_vector(7 downto 0) := "01010101";
begin
    C <= A and B;
    A <= B and C;
    B <= Init when Select = '1' else C;
end arch2;
The easiest way to understand this concept of concurrency is to think of concurrent VHDL statements as a kind of netlist, in which the various assignments being made are nothing more than connections between different types of objects. If you think in terms of a schematic, you might mentally create a picture for the preceding description that looks like this:

(Graphic of schematic netlist)

This is not a particularly useful circuit, but it illustrates the point: if you think of the signals, constants, components, literals and even processes available in concurrent VHDL statements as distinct objects (such as you might find on a schematic or block diagram), and think of operations (such as and, not, and when-else) and assignments as logic gates and wiring specifications, respectively, then you will have no trouble understanding how VHDL's concurrent statements can be mapped to actual digital logic.

Concurrent Signal Assignments

The most common and simple concurrent statements you will write in VHDL are concurrent signal assignments. Concurrent signal assignments such as those shown in the previous section specify the logical relationships between different signals in a digital system.

If you have used PLD-oriented design languages (such as PALASM, ABEL, CUPL or Altera's AHDL), then concurrent signal assignments will be quite familiar to you. Just like the Boolean equations that you write using a PLD language, concurrent signal assignments in VHDL describe logic that is inherently parallel.

Because all signal assignments in your design description are concurrent (including those described within processes, as we will see in the next chapter), there is no relevance to the order in which the assignments are made within the concurrent area of the architecture.

In most cases, you will use concurrent signal assignments to describe either combinational logic (using logic expressions of arbitrary complexity), or you will use them to describe the connections between lower-level components. In some cases (though not typically for designs that will be synthesized) you will use concurrent signal assignments to describe registered logic as well.

The following example includes two simple concurrent signal assignments that represent NAND and NOR operations:

architecture arch3 of nand_circuit is
    signal A, B: std_ulogic;
    signal Y1, Y2: std_ulogic;
begin
    Y1 <= not (A and B);
    Y2 <= not (A or B);
end arch3;
In this example (as in the example presented earlier), there is no significance to the order in which the two assignments have been made. Also, keep in mind that the two signals being assigned (Y1 and Y2) could just as easily have been ports of the entity rather than signals declared in the architecture.

In all cases, signals declared locally (within an architecture, for example) can be used in exactly the same ways as can ports of the corresponding entity. The only difference between ports and locally-declared signals is that ports have a direction, or mode (in, out or inout), limiting whether they can have values assigned to them (in the case of in), or whether they can be read as inputs (in the case of out). If a port is declared as mode out, its value cannot be read. It can only be assigned a value. A port of mode in is the opposite; it can be read, but it cannot be assigned a value. A port of mode inout has both capabilities.

Signal assignments can also include delay specifications, as described later in this chapter.

Conditional Signal Assignment

A conditional signal assignment is a special form of signal assignment, similar to the if-then-else statements found in software programming languages, that allows you to describe a sequence of related conditions under which one or more signals are assigned values. The following example (a simple multiplexer) demonstrates the basic form of a conditional assignment:
entity my_mux is
    port (Sel: in std_ulogic_vector (0 to 1);
             A, B, C, D: in std_ulogic_vector (0 to 3);
             Y: out std_ulogic_vector (0 to 3));
end my_mux;

architecture mux1 of my_mux is
begin

    Y <= A when Sel  = "00" else 
             B when Sel = "01" else
             C when Sel = "10" else
             D when others;

end mux1;
A conditional signal assignment consists of an assignment to one output (or a collection of outputs, such as an array of any type) and a series of conditional when statements as shown. To ensure that all conditions are covered, you can use a terminating when others clause, as was done for the multiplexer description above.

Note: It is very important that all conditions in a conditional assignment are covered, as unwanted latches can be easily generated from synthesis for those conditions that are not covered. In the preceding multiplexer example, you might be tempted to replace the clause D when others with D when Sel = "11" (to improve readability). This would not be correct, however, because the data type being used in the design (std_ulogic_vector) has nine possible values for each bit. This means that there are actually 81 possible unique values that the input Sel could have at any given time, rather than four.

The conditional signal assignment also provides a concise method of describing a list of conditions that have some priority. In the case of the multiplexer just described, there is no priority required or specified, since the four conditions (the possible values of the 2-bit input Sel) are all mutually exclusive. In some design descriptions, however, the priority implied by a series of when-else statements can cause some confusion (and additional logic being generated). For this reason, you might want to use a selected signal assignment (described in the next section) as an alternative.

Selected Signal Assignment

A selected signal assignment is similar to a conditional signal assignment (described in the previous section) but differs in that the input conditions specified have no implied priority. The following is an example of a selected signal assignment:
entity my_mux is
    port (Sel: in std_ulogic_vector (0 to 1);
          A, B, C, D: in std_ulogic_vector (0 to 3);
          Y: out std_ulogic_vector (0 to 3));
end my_mux;

architecture mux1 of my_mux is
begin

    with Sel select
        Y <= A when "00",
             B when "01",
             C when "10",
             D when others;

end mux1;
In this simple multiplexer example, the selected signal assignment has exactly the same function as the conditional signal assignment presented earlier. This is not always the case, however, and you should carefully evaluate which type of assignment is most appropriate for a given application.

Next we get into conditional vs. signal assignments, procedure calls, generate statements, concurrent processes, instantiations, port mapping, and a whole lot more. As you can see, VHDL Made Easy! explains VHDL in plain English with lots of examples. To order your copy with the bonus CD-ROM, click below.


[How to order VHDL Made Easy!]