VHDL coding tips and tricks: August 2016

Friday, August 19, 2016

10 Tips On Writing A Well Formatted VHDL Code

Over the years I have written many technical VHDL posts. But in this post I would like to share some information or tips on writing a well formatted code.

I personally think that a piece of code has to be written with the following points in mind. These points helps another developer, to understand your code faster. Sometimes, even you can look at your codes, years after and not understand what you have done.

These guidelines are written with VHDL programming language in mind, but most of them can be applied to other programming languages as well.

1) Naming the signals or variables in an intuitive way:

It always make sense to name the signals or variables or even entities in an intelligent way. What I mean is when you read a signal name, you should be able to get an idea on what the purpose of that signal is and where it might be connected or used. 
Lets take a simple example.
signal data_from_fifo : unsigned(7 downto 0);
signal data_to_fifo : unsigned(7 downto 0);
If you look at the above two signal declarations you can get some insight into the details of the signals. For example which component its connected to, and whether its an input or output etc.

Another tip would be to CAPITALIZE the constant names. This helps the reader to differentiate it from other signals or variables etc.

2) Use one particular naming style in a project:

This point is in relation with the point (1). There are two ways you can name signals. 

a) With underscores between words as shown in point (1).
b) By capitalizing the first letter of each word. For example,
signal DataFromFifo : unsigned(7 downto 0);
signal DataToFifo : unsigned(7 downto 0);
Whichever naming style you use, stick with that particular style throughout the project.

3) Using 'others' keyword for initializing signals:

In a simple way, you can initialize a signal by writing down all the bits like below:
signal DataFromFifo : unsigned(7 downto 0) := "00000000";
Or you can use, 'others' keyword for this:
signal DataFromFifo : unsigned(7 downto 0) := (others => '0');
As you can see the second way is much more readable. Plus its valid, irrespective of the size of the signal.

4) Consistent Indentation:

Indentation is very important, especially when there is lot of if/else statements, nesting or loops etc. Personally I use tabs(I set it as 4 characters) for this, instead of spaces.

Without proper indentation, its very easy to mix up between different if's else's statements. A well indented code is much easier to debug than one which isn't.

I have take an example from a previous post to show this idea:
process(clk,reset)
begin
if(reset = '1') then
    count <= 0;
elsif(rising_edge(clk)) then
    if(count = 255) then
        count <= 0;
    else
        count <= count + 1;
    end if;
end if;
end process;

5) Keeping the line size less:

The idea here is to make sure that, when the code is opened in most of the devices or editors,it has to fit on the screen width-wise. With this point in mind, its good to keep the number of characters in a line(including spaces) less than 100.

Sometimes the comments are too long, and takes lot of scrolling to read them. In such cases, its advisable to break them into multiple lines in such a way that each line is utmost 100 characters.

6) Comment well, but don't overdo it! 

Its utmost important to write comments along with the code. But you don't need to overdo it. Obvious logic can be left for the reader to decipher themselves. 

For example,
count <= count + 1; --count is incremented.
Its pretty obvious for everyone, what we do in the above line. Its good to avoid such unnecessary comments. 

7) Usage of package file:

A VHDL package file is used when you want to share objects between different components. These objects can be functions, data types etc. 
If the project has lot of functions, then its advisable to keep them in a package file. This increases the readability of the individual component files. 

8) Avoiding duplication of logic:

What I meant here is, avoid duplicating the same lines of codes in different parts of the same component.

For example, suppose you have a communication module project, where you need to find the parity of different signals. Instead of writing the same piece of code over and over again, we can create a function for finding parity and use it multiple times. This will make the code much more readable and less error prone.

9) Avoiding complicated nesting: 

If there is a way to simplify nesting, please do it. Nesting of more than 3 or 4 levels makes the whole code much more harder to debug. This also makes the logic much harder to understand, for another developer.

10) Add a long dummy comment line after each block of logic/process:

To separate a process statement or logic, related to a particular function, from one another, add a dummy line of comment in between.
I use the following comment line for this purpose:
 --********************************************************************--


Do you have more tips which you use in your projects. Please share them here in the comment section. Thanks.



What to be careful of when resetting a 2D RAM

In this post, I want to show you how to reset a RAM using vhdl. I have shown this using a two dimensional RAM, but in effect the method holds for any dimension.

A 2D RAM(Random Access Memory) is basically declared using an array type in VHDL. You can check this post for more information on this.

In the example, I have declared a RAM with width 8 bits and depth 256 elements. You can reset a RAM in two ways.

1)Resetting in one clock cycle:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity top_module is
port(   Clk,reset : in std_logic;   --clock and reset signal
        wr_en : in std_logic;   --write enable signal
        data_in : in unsigned(7 downto 0);  --write input to the RAM
        data_out : out unsigned(7 downto 0);    --read signal from the RAM
        addr_wr : in integer;   --write address
        addr_rd : in integer    --read address
        );
end top_module;

architecture Behavioral of top_module is

--ram type declaration
type ram_type is array(0 to 255) of unsigned(7 downto 0);
signal ram : ram_type;  --definition of ram

begin

--reading and writing of RAM
process(clk)
begin
    if(rising_edge(Clk)) then
        if(reset = '1') then    --synchronous reset
        --all bits reset in one clock cycle
            ram <= (others => (others => '0'));
        else    
            if(wr_en = '1') then    --write to ram when wr_en is high
                ram(addr_wr) <= data_in;
            end if;
            data_out <= ram(addr_rd); --read from ram and output the result
        end if; 
    end if;
end process;    

end Behavioral;

Its easier to write the code for this method as you can see. But this requires writing to all the locations of the RAM in a single clock cycle. So the RAM is implemented on FPGA as a collection of 256 registers, each 8 bits wide. The in-built block RAM available on fpga isn't used, because it's not possible to write to more than two locations of block RAM in one clock cycle. 

This method is fine for small sized RAM's. But if you want to use bigger RAM's and make use of built-in resources in the fpga you need to adopt a different method.

2)Resetting in multiple clock cycles:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity top_module_2 is
port(   Clk,reset : in std_logic;   --clock and reset signal
        wr_en : in std_logic;   --write enable signal
        data_in : in unsigned(7 downto 0);  --write input to the RAM
        data_out : out unsigned(7 downto 0);    --read signal from the RAM
        addr_wr : in integer;   --write address
        addr_rd : in integer    --read address
        );
end top_module_2;

architecture Behavioral of top_module_2 is

--ram type declaration
type ram_type is array(0 to 255) of unsigned(7 downto 0);
signal ram : ram_type;  --definition of ram
signal count : integer := 0;

begin

process(clk)
begin
    if(rising_edge(Clk)) then
        if(reset = '1') then    --synchronous reset
        --reset the ram locations one by one. so it takes 256 clock cycles to reset the ram
            if(count <= 255) then
                count <= count + 1;
                ram(count) <= x"00";
            else
                count <= 0;
            end if;
        else    
            if(wr_en = '1') then    --write to ram when wr_en is high
                ram(addr_wr) <= data_in;
            end if;
            data_out <= ram(addr_rd); --read from ram and output the result
        end if; 
    end if;
end process;    

end Behavioral;

 The resetting part is a bit more complex here. We take 256 clock cycles to reset the whole RAM. The signal 'count' is incremented in every clock cycle and used an an address to the RAM. This way of resetting satisfies the properties of a block RAM. So the synthesis tool uses the in-built block RAM available in fpga for inferring the RAM.

The disadvantage of this method is that, the reset signal have to be applied for a time, proportional to the depth or RAM. For large RAM's, this will cause a large delay. But on the good side, we can use block RAM available inside the FPGA and save the other resources for implementing non-memory logic.