This example is the second of a series of 3. If you didn't yet, please read the Block diagram example first.
With a block diagram that complies with the 10 rules (see the Block diagram example), the VHDL coding becomes straightforward:
Let us illustrate this on the block diagram of a sequential circuit:
The VHDL model of a circuit comprises two compilation units:
clockuses the VHDL type
bit(two values only:
'1'), the entity of our sequential circuit could be:
entity sequential_circuit is port( Data_in: in integer; Clock: in bit; Data_out: out integer ); end entity sequential_circuit;
architecture ten_rules of sequential_circuit is signal Sum, Next_sum: integer; begin <...processes...> end architecture ten_rules;
We have three processes to add to the architecture body, one synchronous (square block) and two combinatorial (round blocks).
A synchronous process looks like this:
process(clock) begin if rising_edge(clock) then o1 <= i1; ... ox <= ix; end if; end process;
i1, i2,..., ix are all arrows that enter the corresponding square block of the diagram and
o1, ..., ox are all arrows that output the corresponding square block of the diagram. Absolutely nothing shall be changed, except the names of the signals, of course. Nothing. Not even a single character.
The synchronous process of our example is thus:
process(clock) begin if rising_edge(clock) then Sum <= Next_sum; end if; end process;
Which can be informally translated into: if
clock changes, and only then, if the change is a rising edge (
'1'), assign the value of signal
Next_sum to signal
A combinatorial process looks like this:
process(i1, i2,... , ix) variable v1: <type_of_v1>; ... variable vy: <type_of_vy>; begin v1 := <default_value_for_v1>; ... vy := <default_value_for_vy>; o1 <= <default_value_for_o1>; ... oz <= <default_value_for_oz>; <statements> end process;
i1, i2,..., in are all arrows that enter the corresponding round block of the diagram. all and no more. We shall not forget any arrow and we shall not add anything else to the list.
v1, ..., vy are variables that we may need to simplify the code of the process. They have exactly the same role as in any other imperative programing language: hold temporary values. They must absolutely be all assigned before being read. If we fail guaranteeing this, the process will not be combinatorial any more as it will model kind of memory elements to retain the value of some variables from one process execution to the next. This is the reason for the
vi := <default_value_for_vi> statements at the beginning of the process. Note that the
<default_value_for_vi> must be constants. If not, if they are expressions, we could accidentally use variables in the expressions and read a variable before assigning it.
o1, ..., om are all arrows that output the corresponding round block of your diagram. all and no more. They must absolutely be all assigned at least once during the process execution. As the VHDL control structures (
case...) can very easily prevent an output signal from being assign, we strongly advice to assign each of them, unconditionally, with a constant value
<default_value_for_oi> at the beginning of the process. This way, even if an
if statement masks a signal assignment, it will have received a value anyway.
Absolutely nothing shall be changed to this VHDL skeleton, except the names of the variables, if any, the names of the inputs, the names of the outputs, the values of the
<default_value_for_..> constants and
<statements>. Do not forget a single default value assignment, if you do the synthesis will infer unwanted memory elements (most likely latches) and the result will not be what you initially wanted.
In our example sequential circuit, the combinatorial adder process is:
process(Sum, Data_in) begin Next_sum <= 0; Next_sum <= Sum + Data_in; end process;
Which can be informally translated into: if
Data_in (or both) change assign the value 0 to signal
Next_sum and then assign it again value
Sum + Data_in.
As the first assignment (with the constant default value
0) is immediately followed by another assignment that overwrites it, we can simplify:
process(Sum, Data_in) begin Next_sum <= Sum + Data_in; end process;
The second combinatorial process corresponds to the round block we added on an output arrow with more than one destination in order to comply with VHDL versions prior 2008. Its code is simply:
process(Sum) begin Data_out <= 0; Data_out <= Sum; end process;
For the same reason as with the other combinatorial process, we can simplify it as:
process(Sum) begin Data_out <= Sum; end process;
The complete code for the sequential circuit is:
-- File sequential_circuit.vhd entity sequential_circuit is port( Data_in: in integer; Clock: in bit; Data_out: out integer ); end entity sequential_circuit; architecture ten_rules of sequential_circuit is signal Sum, Next_sum: integer; begin process(clock) begin if rising_edge(clock) then Sum <= Next_sum; end if; end process; process(Sum, Data_in) begin Next_sum <= Sum + Data_in; end process; process(Sum) begin Data_out <= Sum; end process; end architecture ten_rules;
Note: we could write the three processes in any order, it would not change anything to the final result in simulation or in synthesis. This is because the three processes are concurrent statements and VHDL treats them as if they were really parallel.