Top-down Design and Synthesis Issues for Sequential Processes (that's two tips!)
The Doulos Design Fitness Challenge is an annual event held at an EDA-oriented UK show. The focus of the Challenge this year was on measuring engineers' ability to use VHDL for a real design task.
Engineers were asked to design a counter in VHDL by writing a complete register transfer level VHDL architecture from the following specification and entity declaration. The VHDL description was to be synthesizable, and had to be legal VHDL!
The circuit is an 8 bit synchronous counter, with an active low enable, parallel load, and asynchronous reset. The counter loads or counts only when the enable is active. When the load is active, Data is loaded into count.
The counter has two modes: binary and decade. In binary mode, it is an 8 bit binary counter. In decade mode, it counts in two 4 bit nibbles, each nibble counting from 0 to 9, and the bottom nibble carrying into the top nibble, such that it counts from 00 to 99 decimal.
The truth table of the counter is as follows (- means don't care):
Reset Clock Enable Load Mode Count 0 - - - - 0 1 ^ 1 - - Count 1 ^ 0 0 - Data 1 ^ 0 1 0 Count + 1 (binary) 1 ^ 0 1 1 Count + 1 (decade)
and this is the VHDL entity declaration:
library IEEE; use IEEE.Std_logic_1164.all; entity COUNTER is port (Clock : in Std_logic; Reset : in Std_logic; Enable: in Std_logic; Load : in Std_logic; Mode : in Std_logic; Data : in Std_logic_vector(7 downto 0); Count : out Std_logic_vector(7 downto 0)); end;
So, how do you apply top-down design principles, a knowledge of synthesizable VHDL constructs and good coding finesse to this problem? Let's have a look...
It is important to understand that conceptually, a counter is a register with the output fed back to the input via an incrementer. Hence the VHDL code will reflect this concept. For example,
-- inside a process Q <= Q + 1;
The design challenge introduces some key VHDL coding aspects that need to be borne in mind for synthesis. The most fundamental is the use of the classic asynchronous-reset-plus-clock single-process style of VHDL code.
process (Clock, Reset) begin if Reset = '0' then -- reset register, Q <= 0 elsif RISING_EDGE(Clock) then -- increment register, Q <= Q + 1; end if; end process;
The essence of the code structure is that the clock and reset need to be in the sensitivity list; an appropriate event on either signal will cause one of the two if' branches to execute. Now that we have defined the basic structure of the process, we will go on to fill in the two if' branches.
The reset branch is very simple:
-- inside a process if Reset = '0' then Q <= "00000000"; -- alternatively, Q <= (others => '0');
The clock branch needs to contain the functionality of the other four truth table entries; the code reflects the priority of those inputs directly. The enable signal has the highest priority, with nothing happening when it is high; next in priority is the load signal. Hence inside the enable block we will have,
-- inside a process if Enable = '0' then -- enable counter functionality if Load = '0' then -- load data else -- implement counting based on mode signal end if; end if;
For the actual increment statement (remember, Q <= Q + 1;), it is desirable to combine this functionality for either mode to ensure that only one piece of incrementer hardware is synthesised. So far, the detailed structure of the process has been derived from the truth table in a top-down design manner. Now we need to code the VHDL with a view to its implementation.
-- inside a process if Load = '0' then -- load data else if lower_nibble_count /= max_in_either_mode then -- increment lower nibble else -- wrap lower nibble back to zero if upper_nibble_count /= max_in_either_mode then -- increment upper nibble else -- wrap upper nibble back to zero end if; end if; end if;
Although we are only providing a structure for the detail of the VHDL code in the above code snippet, it is notable that the word increment' appears only twice and that it applies to nibbles - the code is structured to synthesise two 4-bit incrementers. This is a subtle point that was missed by all who entered the challenge!
OK, let's fill out the detail of the code structure presented so far in order to generate a model solution. This is given at the end of the section.
Note that the + operator needs to be overloaded because + is not defined in the IEEE std_logic_1164 package for std_logic_vector types. In the model solution, you will see the use clause, use ieee.numeric_std.all; which makes the overloaded + operator visible to this architecture.
A more subtle aspect is the use of the + operator to produce the incrementer hardware. In order to avoid duplication of incrementers, each nibble is regarded separately. Discounting this approach may lead to the creation of duplicate incrementers. For example,
-- inside a clocked process if mode = '0' then -- 8-bit binary Q <= Q + '1'; else -- two decade counters if Q(3 downto 0) = "1001" then -- wrap lower decade Q(3 downto 0) <= "0000"; if Q(7 downto 4) /= "1001" then -- increment upper decade Q(7 downto 4) <= Q(7 downto 4) + '1'; else -- wrap upper decade Q(7 downto 4) <= "0000"; end if; else -- increment lower decade Q(3 downto 0) <= Q(3 downto 0) + '1'; end if; end if;
This leads to the creation of an 8-bit incrementer for the binary mode of counting, plus one or two individual 4-bit incrementers for the decade count mode. The actual number of incrementers depends on the synthesis tool's ability to share resources.
Finally, note that an internal signal Q was used, rather than the port signal count, which, as an output, may not be read in a process.
In summary, we have applied top-down design principles to create a synthesizable VHDL architecture containing a single process. The detailed code implementation was produced with the pitfalls of synthesis clearly borne in mind.
The rest of this section gives a model solution:
-- counter -- 8 bits -- synchronous, positive edge -- binary/decade -- asynchronous reset, active low -- synchronous parallel load, active low -- synchronous enable, active low -- binary counter mode 0 = 8 bits -- decade counter mode 1 = 2x4 bits -- reset has priority over enable over load library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity COUNTER is port (Clock : in Std_logic; Reset : in Std_logic; Enable: in Std_logic; Load : in Std_logic; Mode : in Std_logic; Data : in Std_logic_vector(7 downto 0); Count : out Std_logic_vector(7 downto 0)); end; architecture Model_Solution of Counter is constant nibble_max : Unsigned(3 downto 0) := "1111"; constant decade_max : Unsigned(3 downto 0) := "1001"; constant zero_nibble : Unsigned(3 downto 0) := "0000"; constant zero_byte : Unsigned(7 downto 0) := "00000000"; signal Q : Unsigned(7 downto 0); begin process (Clock, Reset) begin if Reset = '0' then Q <= zero_byte; elsif RISING_EDGE(Clock) then if Enable = '0' then if Load = '0' then Q <= Unsigned(Data); elsif (Mode = '0' and Q(3 downto 0) /= nibble_max) or (Mode = '1' and Q(3 downto 0) /= decade_max) then Q(3 downto 0) <= Q(3 downto 0) + 1; else Q(3 downto 0) <= zero_nibble; if (Mode = '0' and Q(7 downto 4) /= nibble_max) or (Mode = '1' and Q(7 downto 4) /= decade_max) then Q(7 downto 4) <= Q(7 downto 4) + 1; else Q(7 downto 4) <= zero_nibble; end if; end if; end if; end if; end process; Count <= Std_logic_vector(Q); end;
Your e-mail comments are welcome - send email
Copyright 1995-2014 Doulos