VHDL's process statement is the primary way you will enter sequential statements. A process statement, including all declarations and sequential statements within it, is actually considered to be a single concurrent statement within a VHDL architecture. This means that you can write as many processes and other concurrent statements as are necessary to describe your design, without worrying about the order in which the simulator will process each concurrent statement.

 

Anatomy of a Process

The general form of a process statement is:

process_name: process (sensitivity_list)

    declarations

begin

    sequential_statements

end process;

 

The easiest way to think of a VHDL process is to relate it to event-driven software—like a program that executes (in simulation) any time there is an event on one of its inputs (as specified in the sensitivity list). A process describes the sequential execution of statements that are dependent on one or more events having occurred. A flip-flop is a perfect example of such a situation. It remains idle, not changing state, until there is a significant event (either a rising edge on the clock input or an asynchronous reset event) that causes it to operate and potentially change its state.

Although there is a definite order of operations within a process (from top to bottom), you can think of a process as executing in zero time. This means that a process can be used to describe circuits functionally, without regard to their actual timing, and multiple processes can be "executed" in parallel with little or no concern for which processes complete their operations first.

A process can be thought of as a single concurrent statement written within a VHDL architecture, extending from the process keyword (or from the optional process name that precedes it) to the terminating end process keyword pair and semicolon.

The process name (process_name) appearing before the process keyword is optional and can be used to: (1) identify specific processes that are executing during simulation, and (2) more clearly distinguish elements such as local variables that may have common names in different processes.

Immediately following the process statement is an optional list of signals enclosed by parentheses. This list of signals, called the sensitivity list, specifies the conditions under which the process is to begin executing. When a sensitivity list is associated with a process, any change in the value of any input in the list will result in immediate execution of the process.

In the absence of a sensitivity list, the process will execute continuously, but must be provided with at least one wait statement to cause the process to suspend periodically. Examples of processes that are written with and without sensitivity lists are presented in the subsections below.

The order in which statements are written in a process is significant. You can think of a process as a kind of software program that is executed sequentially, from top to bottom, each time it is invoked during simulation. Consider, for example, the following process describing the operation of a counter:

    process(Clk)

    begin

        if Clk = '1' and Clk’event then

            if Load = '1' then

                Q <= Data_in;

            else

                Q <= Q + 1;

            end if;

        end if;

    end process;

 

When this process is executed, the statements appearing between the begin and end process statements are executed in sequence. In this example, the first statement is an if test that will determine if there was a rising edge on the Clk clock input. A second, nested if test determines if the counter should be loaded with Data_in or incremented, depending on the value of the Load input.

When is a process invoked? That depends on the type of process it is. There are two fundamental types of processes that you can write: those that have sensitivity lists and those that do not.

Processes With Sensitivity Lists

A process with a sensitivity list is executed during simulation whenever an event occurs on any of the signals in the sensitivity list. An event is defined as any change in value of a signal, such as when a signal of type Boolean changes from True to False, or when the value of an integer type signal is incremented or otherwise modified.

Processes that include sensitivity lists are most often used to describe the behavior of circuits that respond to external stimuli. These circuits, which may be either combinational, sequential (registered), or a combination of the two, are normally connected with other sub-circuits or interfaces, via signals, to form a larger system. In a typical circuit application, such a process will include in its sensitivity list all inputs that have asynchronous behavior. These inputs may include clocks, reset signals, or inputs to blocks of combinational logic.

The following is an example of a process that includes a sensitivity list. This process describes the operation of a clocked shift register with an asynchronous reset; note the use of the `event signal attribute to determine which of the two signals (Clk and Rst) had an event:

    process(Rst, Clk)

    begin

        if Rst = '1' then

            Q <= "00000000";

        elsif Clk = '1' and Clk'event then

            if Load = '1' then

                Q <= Data_in;

            else

                Q <= Q(1 to 7) & Q(0);

            end if;

        end if;

    end process;

 

During simulation, whenever there is an event on either Rst or Clk, this process statement will execute from the begin statement to the end process statement pair. If the Rst input is '1' (regardless of whether the event that triggered the process execution was Rst or Clk), then the output Q is set to a reset value of "00000000". If the value of Rst is not '1', then the Clk input is checked to determine if it has a value of '1' and had an event. This checking for both a value and an event is a common (and synthesizable) way of detecting transitions, or edges, on signals such as clocks.

After all of the statements in the process have been analyzed and executed, the process is suspended until a new event occurs on one of the process’ sensitivity list entries.

For design descriptions intended for input to synthesis software, you should follow the above example and write process statements that include sensitivity lists, as this is the most widely used synthesis convention for registers.

Processes Without Sensitivity Lists

A process that does not include a sensitivity list executes somewhat differently than a process with a sensitivity list. Rather than executing from the begin statement at the top of the process to the end process statement, a process with no sensitivity list executes from the beginning of the process to the first occurrence of a wait statement, then suspends until the condition specified in the wait statement is satisfied. If the process only includes a single wait statement, the process re-activates when the condition is satisfied and continues to the end process statement, then begins executing again from the beginning. If there are multiple wait statements in the process, the process executes only until the next wait statement is encountered.

The following example demonstrates how this works, using a simplified manchester encoder as an example:

    process

    begin

        wait until Clk = '1' and Clk'event;

        M_out <= data_in;

        wait until Clk = '1' and Clk'event;

        M_out <= not data_in;

    end process;

 

This process will suspend its execution at two points. The first wait until statement suspends the process until there is a rising edge on the clock (a transition to a value of '1'). When this rising edge condition has been met, the process continues execution by assigning the value of data_in to M_out. Next, the second wait until statement suspends the process until another rising edge has been detected on Clk. When this condition has been met, the process continues and assigns the inverted value of data_in to M_out. The process does not suspend at the end process statement, but instead loops back to the beginning and immediately starts processing over again.

The use of multiple wait statements within a process makes it possible to describe very complex multiple-clock circuits and systems. Unfortunately, such design descriptions usually fall outside of the scope of today's synthesis tools. Rather than use multiple wait statements to describe such logic, you will probably use wait statements only when describing test stimulus, as discussed later in this chapter.

Using Processes for Combinational Logic

In the previous chapter, we saw how concurrent signal assignments can be used to create combinational logic. When you write a sequence of concurrent signal assignments, each statement that you write is independent of all other statements and results in a unique combinational function (unless a guarded block or some other special feature is used to imply memory).

If you wish, you can use sequential VHDL statements (in the form of a process or subprogram) to create combinational logic as well. Sequential VHDL statements can actually be more clear and concise for many types of combinational functions, as they allow the priority of operations to be clearly expressed within a combinational logic function.

The following is an example of a simple combinational logic function (a 4-into-1 multiplexer) described using a process:

entity simple_mux is

    port (Sel: in bit_vector (0 to 1);

             A, B, C, D: in bit;

             Y: out bit);

end simple_mux;

 

architecture behavior of simple_mux is

begin

    process(Sel, A, B, C, D)

    begin

        if Sel = "00" then

            Y <= A;

        elsif Sel = "01" then

            Y <= B;

        elsif Sel = "10" then

            Y <= C;

        elsif Sel = "11" then

            Y <= D;

        end if;

    end process;

end simple_mux;

 

This simple process describes combinational logic because it conforms to the following rules:

1.  The sensitivity list of the process includes all signals that are being read (i.e., used as inputs) within the process.

2.  Assignment statements written for the process outputs (in this case only output Y) cover all possible combinations of the process inputs (in this case Sel, A, B, C and D).

These two rules dictate whether the signal assignment logic generated from a process is strictly combinational or will require some form of memory element (such as a flip-flop or latch).

For processes that include variable declarations, there is an additional rule that comes into play:

3.  All variables used in the process must have a value assigned to them before they are read (i.e., used as inputs).

An example of when an apparently combinational logic description actually describes registered logic is demonstrated by the modified (6-into-1) multiplexer description shown below:

entity simple_mux is

    port (Sel: in bit_vector (0 to 2);

             A, B, C, D, E, F: in bit;

             Y: out bit);

end simple_mux;

 

architecture behavior of simple_mux is

begin

    process(Sel, A, B, C, D, E, F)

    begin

        if Sel = "000" then

            Y <= A;

        elsif Sel = "001" then

            Y <= B;

        elsif Sel = "010" then

            Y <= C;

        elsif Sel = "011" then

            Y <= D;

        elsif Sel = "100" then

            Y <= E;

        elsif Sel = "101" then

            Y <= F;

        end if;

    end process;

end simple_mux;

 

This modified version of the multiplexer has only six of the eight possible values for Sel described in the if-then-elsif statement chain. What happens when Sel has a value of "110" or "111"?  Unlike many simpler hardware description languages (most notably languages such as ABEL or CUPL that are intended for programmable logic use), the default behavior in VHDL is to hold the values of unspecified signals. For output Y to hold its value when Sel has a value of "110" or "111", a memory element (such as a latch) will be required. The result is that the circuit as described is no longer a simple combinational logic function.

Understanding what types of design descriptions will result in combinational logic and what types will result in latches and flip-flops is very important when writing VHDL for synthesis.  For more information, see Appendix A, Getting The Most Out Of Synthesis.

Using Processes for Registered Logic

Perhaps the most common use of VHDL processes is to describe the behavior of circuits that have memory and must save their state over time. The sequential nature of VHDL processes (and subprograms) make them ideal for the description of such circuits.

In the previous section we listed three rules that must be obeyed in order to ensure that the circuitry described by a process is combinational. If your goal is to create registered logic (using either flip-flop or latch elements), then you will describe your design using one or more of the following methods:

1.  Write a process that does not include all of its inputs in the sensitivity list.

2.  Use incompletely specified if-then-elsif logic to imply that one or more signals must hold their values under certain conditions.

3.  Use one or more variables in such a way that they must hold a value between iterations of the process. (For example, specify a variable as an input to an assignment before that variable has been assigned a value itself.)

To ensure the highest level of compatibility with synthesis tools, you should use a combination of methods 1 and 2. The following example demonstrates how registered logic (the shift register presented in Chapter 2, A First Look At VHDL) can be described using a process:

-------------------------------------------------------

-- Eight-bit  shifter

--

library ieee;

use ieee.std_logic_1164.all;

entity rotate is

    port( Clk, Rst, Load: in std_logic;

              Data: in std_logic_vector(0 to 7);

              Q: out std_logic_vector(0 to 7));

end rotate;

 

architecture rotate1 of rotate is

    signal Qreg: std_logic_vector(0 to 7);

begin

    reg: process(Rst,Clk)

    begin

        if Rst = ‘1’ then   -- Async reset

            Qreg <= "00000000";

        elsif (Clk = ‘1’ and Clk’event) then

            if (Load = ‘1’) then

                Qreg <= Data;

            else

                Qreg <= Qreg(1 to 7) & Qreg(0);

            end if;

        end if;

    end process;

    Q <= Qreg;

end rotate1;

 

In this example, the incomplete if-then statement implies that signal Qreg will hold its value when the two conditions (a reset or clock event) are false.

For a detailed explanation of this example, see Chapter 2, A First Look At VHDL.

Using Processes for State Machines

State machines are a common form of sequential logic circuits that are used for generating or detecting sequences of events. To describe a synthesizable state machine in VHDL, you should follow a well-established coding convention that makes use of enumerated types and processes. The following example demonstrates how to write a synthesizable state machine description using this coding convention.

The circuit, a video frame grabber controller, was first described (in the form of an ABEL language description) in Practical Design Using Programmable Logic by David Pellerin and Michael Holley (Prentice Hall, 1990).

The circuit described is a simple freeze-frame unit that grabs and holds a single frame of NTSC color video image. This design description includes the frame detection and capture logic. The complete circuit requires an 8-bit D-A/A-D converter and a 256K X 8 static RAM.

The design description makes use of a number of independent processes. The first process (which has been given the name of ADDRCTR), describes a large counter corresponding to the frame address counter in the circuit. This counter description makes use of the IEEE Standard 1076.3 numeric data type unsigned.

The second process, SYNCCTR, also describes a counter using the unsigned data type. This counter is used to detect the vertical blanking interval, which indicates the start of one frame of video.

The third and fourth processes (STREG and STTRANS) describe the operation of the video frame grabber controller logic, using the most common (and most easily synthesized) form for state machines. First, an enumerated type called states is declared that consists of the values StateLive, StateWait, StateSample, and StateDisplay. Two intermediate signals (current_state and next_state) are then introduced to represent the current state and calculated next state of the machine. In the processes that follow, signal current_state represents a set of state registers, while next_state represents a combinational logic function.

The diagram of Figure 6-1 illustrates the operation of the video frame grabber controller:

 

        

 

Process STREG describes the operation of the state registers, and simply loads the value of the calculated next state (signal next_state) into the state registers (current_state) whenever there is a synchronous clock event. This process also includes asynchronous reset logic that will set the machine to its initial state (StateLive) when the Rst input is asserted.

The actual transition logic for the state machine is described in process STTRANS. In this process, a case statement is used to decode the current state of the machine (as represented by signal current_state) and define the transitions between states. This is an example where sequential VHDL statements are used to describe non-sequential (combinational) logic.

-------------------------------------------------------

-- A Video Frame Grabber.

--

library ieee;

use ieee.std_logic_1164.all;

use ieee.numeric_std.all;

 

entity video is

    port (Reset, Clk: in std_logic;

          Mode: in std_logic;

          Data: in std_logic_vector(7 downto 0);

          TestLoad: in std_logic;

          Addr: out std_logic_vector(17 downto 0);

          RAMWE: out std_logic;

          RAMOE: out std_logic;

          ADOE: out std_logic);

end video;

 

architecture control1 of video is

    constant FRAMESIZE: integer := 253243;

    constant TESTADDR: integer := 253000;

 

    signal ENDFR: std_logic;

    signal INCAD: std_logic;

    signal VS: std_logic;

    signal Sync: unsigned (6 downto 0);

begin

 

    -- Address counter. This counter increments until we reach the end of

    -- the frame (address 253243), or until the input INCAD goes low.

 

    ADDRCTR: process(Clk)

        variable cnt: unsigned (17 downto 0);

    begin

        if rising_edge(Clk) then

            if TestLoad = ‘1’ then

                cnt := to_unsigned(TESTADDR,18);

                ENDFR <= ‘0’;

            else

                if INCAD = ‘0’ or cnt = FRAMESIZE then

                    cnt := to_unsigned(0,18);

                else

                    cnt := cnt + to_unsigned(1,18);

                end if;

                if cnt = FRAMESIZE then

                    ENDFR <= ‘1’;

                else

                    ENDFR <= ‘0’;

                end if;

            end if;

        end if;

        Addr <= std_logic_vector(cnt);

    end process;

 

    -- Vertical sync detector. Here we look for 128 bits of zero, which

    -- indicates the vertical sync blanking interval.

    SYNCCTR: process(Reset,Clk)

    begin

        if Reset = ‘1’ then

            Sync <= to_unsigned(0,7);

        elsif rising_edge(Clk) then

            if Data /= "00000000" or Sync = 127 then

                Sync <= to_unsigned(0,7);

            else

                Sync <= Sync + to_unsigned(1,7);

            end if;

        end if;

    end process;

 

    VS <= ‘1’ when Sync = 127 else ‘0’;

 

    STATEMACHINE: block

        type states is (StateLive,StateWait,StateSample,StateDisplay);

        signal current_state, next_state: states;

    begin

       -- State register process:

     STREG: process(Reset,Clk)

        begin

            if Reset = ‘1’ then

                current_state <= StateLive;

            elsif rising_edge(Clk) then

                current_state <= next_state;

            end if;

        end process;

    

        -- State transitions:

        STTRANS: process(current_state,Mode,VS,ENDFR)

        begin

            case current_state is

                when StateLive =>    -- Display live video on the output

                    RAMWE <= ‘1’;

                    RAMOE <= ‘1’;

                    ADOE <= ‘0’;

                    INCAD <= ‘0’;

                    if Mode = ‘1’ then

                       next_state <= StateWait;

                    else

                       next_state <= StateLive

                    end if;

                when StateWait =>    -- Wait for vertical sync

                    RAMWE <= ‘1’;

                    RAMOE <= ‘1’;

                    ADOE <= ‘0’;

                    INCAD <= ‘0’;

                    if VS = ‘1’ then

                       next_state <= StateSample;

                    else

                       next_state <= StateWait

                    end if;

                when StateSample =>  -- Sample one frame of video

                    RAMWE <= ‘0’;

                    RAMOE <= ‘1’;

                    ADOE <= ‘0’;

                    INCAD <= ‘1’;

                    if ENDFR = ‘1’ then    

                       next_state <= StateDisplay;

                    else

                       next_state <= StateSample

                    end if;

                when StateDisplay => -- Display the stored frame

                    RAMWE <= ‘1’;

                    RAMOE <= ‘0’;

                    ADOE <= ‘1’;

                    INCAD <= ‘1’;

                    if Mode = ‘1’ then

                       next_state <= StateLive;

                    else

                       next_state <= StateDisplay

                    end if;

            end case;

        end process;

    end block;

end control1;

Specifying State Machine Encodings

We have described the preceding video frame grabber in an implementation-independent fashion, with the assumption that whatever synthesis tool we use to process this design will come up with an optimal solution, in  terms of the state encodings selected. For small designs such as this, or when you are not tightly constrained for space, it is probably fine to let the synthesis tool encode your states for you. In many cases, however, you will have to roll up your sleeves and work on improving the synthesis results yourself, by creating your own optimizal state encodings. Determining an optimal encoding for a large state machine can be a long and tedious process, the methods for which are beyond the scope of this book. It is important to understand the various coding styles for manually-encoded machines, however, to get the most out of synthesis.

Using Constants for State Encodings

The easiest way to specify an explicit encoding for a state machine is to replace the declaration and use of an enumerated type with a series of constant declarations. For the video frame grabber, for example, we could replace the declarations:

        type states is (StateLive,StateWait,StateSample,StateDisplay);

        signal current_state, next_state: states;

 

with:

    type states is std_logic_vector(1 downto 0);

    constant StateLive: states := "00";

    constant StateWait: states := "01";

    constant StateSample: states := "11";

    constant StateDisplay: states := "10";

    signal current_state, next_state: states;

 

Using these declarations will result in the precise encodings that we have specified in the synthesized circuit. There is one additional modification we will have to make to our frame grabber state machine if we specify the states using declarations based on std_logic_vector, however. Because the base type of std_logic_vector (std_logic) has nine unique values, the four constants that we have declared (StateLive, StateWait, StateSample and StateDisplay) do not represent all possible values for the state type. For this reason, we will have to add an others clause to the case statement describing the transitions of our machine, as in:

    when others =>

        null;

 

Using the Enum_encoding Synthesis Attribute

An alternate method of specifying state machine encodings is provided in some synthesis tools, inluding Synopsys, Exemplar, and Metamor. This method makes use of a non-standard (but widely supported) attribute called enum_encoding. The following modified declarations (again, using the video frame grabber state machine as an example) uses the enum_encoding attribute to specify the same state encoding used in the previous example:

        type states is (StateLive,StateWait,StateSample,StateDisplay);

        attribute enum_encoding of states: type is "00 01 11 10";

       signal current_state, next_state: states;

 

The enum_encoding attribute used in this example has been defined elsewhere (most probably in a special library package provided by the synthesis vendor) as a string:

        attribute enum_encoding: string;

 

This attribute is recognized by the synthesis tool, which encodes the generated state machine circuitry accordingly. During simulation, the enum_encoding attribute is ignored, and we will instead see the enumerated values displayed.

Specifying a One-hot Encoding

One common technique for optimizing state machine logic is to use what is called a one-hot encoding, in which there is one register dedicated to each state in the machine. One-hot machines require more register resources than more typical, maximally-encoded machines, but can result in tremendous savings in the combinational logic required for next-state and output decoding. This trade-off can be particularly effective in device technologies that have an abundance of built-in registers, but that suffer from limited (or relatively slow) routing resources.

When you first try to use a one-hot approach to state encoding, it is tempting to describe the machine using the same methods that you might have used for your other state machines. The following declarations represent an attempt to encode our video frame grabber state machine one-hot using constant declarations:

    type states is std_logic_vector(3 downto 0);

    constant StateLive: states := "0001";

    constant StateWait: states := "0010";

    constant StateSample: states := "0100";

    constant StateDisplay: states := "1000";

    signal current_state, next_state: states;

 

At first glance this looks correct; each state is represented by a single bit being asserted, and when simulated and synthesized, the machine will indeed transition to the appropriate encoded state for each transition described in the case statement shown earlier. In terms of the logic required for state decoding, however, we have not achieved a genuine one-hot machine. This is because the case statement we have written describing the state transitions implicitly refers to all four state registers when decoding the current state of the machine. A true, optimal one-hot machine only requires that one register be observed to determine if the machine is in a given state.

To generate the correct logic, optimized as a one-hot encoded machine, we have to modify the description somewhat, so that only one state register is examined for each possible transition. The easiest way to do this is to replace the case statement with a series of if statements, as follows:.

     -- State transitions for one-hot encoding:

        STTRANS: process(current_state,Mode,VS,ENDFR)

        begin

            if current_state(0) = ‘1’ then    -- StateLive

                    RAMWE <= ‘1’;

                    RAMOE <= ‘1’;

                    ADOE <= ‘0’;

                    INCAD <= ‘0’;

                    if Mode = ‘1’ then

                       next_state <= StateWait;

                    else

                       next_state <= StateLive

                    end if;

            end if;

            if current_state(1) = ‘1’ then    -- StateWait

                    RAMWE <= ‘1’;

                    RAMOE <= ‘1’;

                    ADOE <= ‘0’;

                    INCAD <= ‘0’;

                    if VS = ‘1’ then

                       next_state <= StateSample;

                    else

                       next_state <= StateWait

                    end if;

            end if;

            if current_state(2) = ‘1’ then    -- StateSample

                    RAMWE <= ‘0’;

                    RAMOE <= ‘1’;

                    ADOE <= ‘0’;

                    INCAD <= ‘1’;

                    if ENDFR = ‘1’ then    

                       next_state <= StateDisplay;

                    else

                       next_state <= StateSample

                    end if;

            end if;

            if current_state(3) = ‘1’ then    -- StateDisplay

                    RAMWE <= ‘1’;

                    RAMOE <= ‘0’;

                    ADOE <= ‘1’;

                    INCAD <= ‘1’;

                    if Mode = ‘1’ then

                       next_state <= StateLive;

                    else

                       next_state <= StateDisplay

                    end if;

            end if;

        end process;

 

We could, of course, make this description more readable by introducing constants for the index values for each state register.

Using Processes for Test Stimulus

In addition to their use for describing combinational and registered circuits to be synthesized or modeled for simulation, VHDL processes are also important for describing the test environment in the form of sequential application of stimulus and (if desired) checking of resulting circuit outputs.

A process that is intended for testing (as part of a test bench) will normally have no sensitivity list.  Instead, it will have a series of wait statements that provide time for the unit under test to stabilize between the assignment of test inputs. Because a process intended for use as a test bench does not describe hardware to be synthesized, you are free to use any legal features and style of VHDL without regard to the limitations of synthesis.

The following is a simplistic test bench example written with a single process statement. This process statement might be used to apply a sequence of input values to a lower-level circuit and check the state of that circuit's outputs at various points in time.

    -- A simple process to apply various stimulus over time...

    process

        constant PERIOD: time := 40 ns;

    begin

        Rst <= '1';

        A <= "00000000";

        B <= "00000000";

        wait for PERIOD;

        CheckState(Q, "00000000");

        Rst <= '0';

        A <= "10101010";

        B <= "01010101";

        wait for PERIOD * 4;

        CheckState(Q, "11111111");

        A <= "11111010";

        B <= "01011111";

        wait for PERIOD * 2;

        CheckState(Q, "00110101");

        wait;

    end process;

 

In this example, the process executes just once before suspending indefinitely (as indicated by the final wait statement). The stimulus is described by a sequence of assignments to signals A and B, and by calls to a procedure (defined elsewhere) named CheckState.  Wait statements are used to describe a delay between each test sequence.

More comprehensive examples of using processes for test stimulus can be found in Chapter 9, Writing Test Benches.