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:
clock
uses the VHDL type bit
(two values only: '0'
and '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;
where 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 ('0'
to '1'
), assign the value of signal Next_sum
to signal Sum
.
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;
where 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 (if
, 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 Sum
or 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.