Programmable Logic/VHDL Processes
Concurrent vs. Sequential evaluation
editThere are two ways to make signal assignments in VHDL. Signal assignments can be made through a concurrent statement or a sequential statement. Assignments in concurrent statements take effect immediately. In the following example code, it can be thought that sQ will always have the value of sD when sFlag = '1'. Otherwise it will be zero. This will take effect one simulation "tick" after sD or sFlag changes.
architecture rtl of some_entity
signal sFlag: std_logic;
signal sD: std_logic;
signal sQ: std_logic;
begin
with sFlag select
sQ <= sD when '1',
'0' when others;
end architecture some_entity;
Using a process allows us to write functionally equivalent code, only using more natural sequential statements. In a process, the statements are executed in order and only the end result of all the assignments in the process actually take effect. It is convenient to think of processes as mini "programs" that are executed and then apply the result of their signal assignments when done. Functionally equivalent sequential code is shown below:
architecture rtl of some_entity
signal sFlag: std_logic;
signal sD: std_logic;
signal sQ: std_logic;
begin
process(sFlag, sD)
begin
sQ <= '0';
if(sFlag = '1') then
sQ <= sD;
end if;
end process;
end architecture some_entity;
When tracing through this code, sQ is first assigned the value of zero. Then, IF sFlag = '1', sQ is assigned the value of sD. sQ is not updated until the entire process is executed. Since the whole process is executed in a single simulation "tick" the end result is identical to the code with a concurrent assignment.
Processes using "wait"
editOne way processes can be constructed is using wait statements. A wait statement is a VHDL construct that tells the simulator how long do delay execution or under what condition to continue execution. The following are examples of wait statements:
-- Tells the simulator to delay evaluation of the rest of the process for 10 ns;
wait for 10 ns;
-- Tells the simulator to delay evaluation of the rest of the process until a_flag contains the value of '1'
wait until a_flag = '1';
By using wait statements the simulator knows when to stop execution of a process and let time advance in the simulation. Wait statements are used in a process as in the example below:
signal sClk: std_logic := '0';
constant cClkPeriod: time := 10 ns;
...
pClockSim: process
begin
sClk <= not sClk;
wait for cClkPeriod/2;
end process pClockSim;
In this example the signal sClk is assigned the opposite of its current value. The process will then wait for half the time specified by the constant cClkPeriod, after which execution will continue. When the end of a process is reached, evaluation continues immediately back at the beginning of a process.
Processes using wait statements are usually not synthesizable and are typically used in test benches. However, they are usually intuitive to use and allow the rapid construction of test bench modules and the generation of other test signals used for checking the functionality of a VHDL module.
Processes using a sensitivity list
editAnother way to use processes is to use a sensitivity list:
...
architecture rtl of some_entity is
signal a_flag: std_logic;
signal another_flag: std_logic;
...
begin
...
process(another_flag)
begin
a_flag <= another_flag;
end process;
...
end architecture rtl;
The sensitivity list (another_flag) is a comma separated list of signals that the simulator watches for changes. When these signals change, the simulator will evaluate the process and update the assigned signals. With the above example, the process will be executed any time the signal "another_flag" changes.
Concurrent Statements as Processes
editConcurrent statements in an architecture can be interpreted as a process with a single statement.
...
architecture rtl of some_entity is
signal a_flag: std_logic;
signal another_flag: std_logic;
...
begin
...
process(another_flag)
begin
a_flag <= another_flag;
end process;
...
end architecture rtl;
This code can be abbreviated as:
...
architecture rtl of some_entity is
signal a_flag: std_logic;
signal another_flag: std_logic;
...
begin
...
a_flag <= another_flag;
...
end architecture rtl;
Concurrent statements in an architecture simplify the syntax of a module and allow simple assignments to be made with fewer VHDL constructs.
Synthesis of processes
editSince the concept of scheduling evaluation of processes does not translate directly to physical logic, processes are interpreted to represent blocks of combinatorial logic or registers. A synthesis utility will usually interpret a block of combinatorial logic from a process with a sensitivity list.
architecture rtl of some_entity
signal sFlag: std_logic;
signal sD: std_logic;
signal sQ: std_logic;
begin
process(sFlag, sD)
begin
sQ <= '0';
if(sFlag = '1') then
sQ <= sD;
end if;
end process;
end architecture some_entity;
This example from earlier will be synthesized into the mux below:
Aside from combinatorial logic, in order to create useful digital logic circuits, we need a way to create a register in hardware. A register (flip-flop) can be created using a process template similar to the following:
process(clk, reset)
begin
if(reset = '1') then
sQ <= '0';
elsif rising_edge(clk) then
sQ <= sQ_next;
end if;
end process
When synthesized, this code will create a register that has an asynchronous active high reset with a "D" input of sQ_next and an output of sQ.
Processes with sensitivity lists used to describe combinatorial logic and "register template" processes can be used together to describe a RTL digital circuit:
signal clk: std_logic;
signal reset: std_logic;
signal sQ: std_logic;
signal sQ_next: std_logic;
...
-- Combinatorial logic that assigns a value to sQ_next
process(sQ)
begin
sQ_next <= not sQ;
end process;
-- This process will create a register that will store the value of sQ_next for one
-- clock cycle
process(clk, reset)
begin
if(reset = '1') then
sQ <= '0';
elsif rising_edge(clk) then
sQ <= sQ_next;
end if;
end process
Again, the combinatorial parts of the RTL circuit are expressed using processes with sensitivity lists. Registers are then created using a standard register template as above.
Process coding tips
edit- When describing combinatorial logic using a process, always "initialize" all the assigned signals to a default value at the top of the process.
- Although shorter, one author's experience has shown that separating RTL logic into two processes (one for combinatorial logic and one for registers) is less error prone. The uses of signals such as "sQ" for the current output of a register and "sQ_next" for the output of the combinatorial logic and the next clock output of the register facilitates this.