VHDL coding tips and tricks: functions
Showing posts with label functions. Show all posts
Showing posts with label functions. Show all posts

Wednesday, February 16, 2011

Be careful when using functions in your code.

There are many coding styles in VHDL where the code which work well in the simulation will not work on the actual fpga. In this article I am going to talk about one such RTL coding style.

As you know VHDL programmers normally use functions to represent combinational logic. But using functions every where without much thinking may result in dangerous bugs in the code. One perfect example is a function which replaces the code for a latch.

See the below code, which doesn't use a function:


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity test is
port(   en1,a,Clk : in std_logic;                    
      output : out std_logic );
end test;

architecture BEHAVIORAL of test is

begin

process(Clk)
begin    
    if ( rising_edge(Clk) ) then      
        if(en1 = '1') then
            output <= a;
        end if;
   end if;
end process;

end BEHAVIORAL;

In the above code, whenever signal en1 goes high, at the positive edge of Clk we assign a to output port. We don't specify what will happen when the value of en1 is '0'. So basically the code results in a latch.
When synthesised, XST(Xilinx synthesis tool) uses a FDE flip flop for implementing the code. If you check the link pointed by FDE, you can see the truth table of FDE flip flop. As expected, it works like a latch. Whenever CE is '0' the flip flop retains the previous output.

Now I am going re-write the above code using functions. I will replace some part of the combinational logic inside the process with a function. See the code below:


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity test is
port(   en1,a,Clk : in std_logic;                    
      output : out std_logic );
end test;

architecture BEHAVIORAL of test is

function latch (en1,a : std_logic ) return std_logic is
variable output : std_logic := '0';
begin
    if(en1 = '1') then
        output := a;
    end if;
return output;
end latch; 

begin

process(Clk)
begin    
    if ( rising_edge(Clk) ) then      
        output <= latch(en1,a);
   end if;
end process;

end BEHAVIORAL;

At the first look the above code is same as the first code. To confirm both has the same functionality, I simulated them. The outputs are matching. So they indeed work the same way in the simulation. But wait a minute. How about the synthesis results?

I ran XST for the above code and checked the technology viewer to see the synthesised circuit. What I saw was, instead of a FDE flipflop, XST used a FDR flipflop this time. Go to the link pointed by FDR and check the truth table given there. You can see that its not a latch. When R is '1' the flipflop output is reset to '0' rather than maintaining the previous value. So both the codes are going to work differently on board.

How did this happen? This is because all functions are synthesised into pure combinational logic by the synthesis tool. This means that you cannot go on using functions every where you want without proper brain storming. If you do, then the pre and post synthesis results may vary.

Note:- Both the codes where simulated and synthesized using Xilinx Webpack 12.1. The results may a vary a little depending on the tool you are using.

Wednesday, March 3, 2010

VHDL: How to use Packages in your design - with Example code!

What is a Package?

A Package is a VHDL file, which can be used to contain user defined data types,constants, functions, procedures etc. A single package can be shared across many VHDL designs.

Uses of Packages:

1) To keep user defined functions and procedures in a common place:

Consider you are implementing a big project with lot of different VHDL designs connected with each other. Some of these modules might need code parts dealing with the same functionality. For example a Binary to BCD converter might be used in many modules.

Without a package file, you would have to copy and paste this function, in each and every module which is using it. But with the concept of packages, we just have to write the code once in a package file and then add just two lines in your modules to point towards the package contents.

2) To declare custom data types:

Some times, your design might have input or output ports which cannot be represented by a usual integer/std_logic_vector/unsigned type. In this case you can define the custom data type in the package and include the package name in the file.

For example, consider an 8 point FFT design with 8 complex inputs and 8 complex outputs. The entity port list would be too long if we don't have a custom data type for this. You might want to see this example to understand what I meant.

I will go into these points in a bit.

What is a Library?

A library is a collection of related packages. You might have not realized it already, but you have been already using packages and libraries in your designs.

The first two lines in most of your vhdl designs are normally this:

library ieee;
use ieee.std_logic_1164.all;

What are we doing here?

First we tell the compiler to use the library named ieee.
Then we tell the compiler to use the package named std_logic_1164 which is part of ieee library. This is how we normally use a package in a VHDL design.

For a custom written package, the compiler compiles the package into a default directory called work. Suppose we write a package called test_pkg, then to use it in your design we include the following two lines,

library work; use work.test_pkg.all;

How does a Package look like?

A package is declared in the following format:

package package_name is
     -- Declaration of
          -- types and subtypes
          -- subprograms
          -- constants, signals etc.
end package_name;


package body package_name is
     -- Definition of previously declared
        -- constants
        -- subprograms
     -- Declaration/definition of additional
        -- types and subtypes
        -- subprograms
        -- constants, signals and shared variables
end package_name;

An example Package: ( test_pkg.vhd ) 

--declare the libraries and packages which are used by this custom package
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package test_pkg is --name of the package is "test_pkg"

--Define a data type called t1(totally 32 bits contains 3 different fields)
type t1 is  
    record
        a : unsigned(11 downto 0);  --12 bit field.
        b : unsigned(15 downto 0);  --16 bit field.
        c : unsigned(3 downto 0);   --4 bit field.
    end record;

--Declare a function named "add".
function xored (a2 : t1; b2: t1) return t1;

end test_pkg;   --end of package.

package body test_pkg is  --start of package body

--definition of function we declared above
--The function take two t1 data types and calculate the xor of each fields.
function  xored (a2 : t1; b2: t1) return t1 is
    variable temp : t1;
begin -- Just name the fields in order...
    temp.a:=a2.a xor b2.a;
    temp.b:=a2.b xor b2.b;
    temp.c:=a2.c xor b2.c;
    return temp;
end xored;
--end function

end test_pkg;  --end of the package body

In the above package, we declare a custom data type called t1 using a record. We also define a vhdl function named xored, which basically does xor operation between two of these newly declared data types.

To use this package in your vhdl design, save the above code in a file named test_pkg.vhd and add it to the current project and compile it. As mentioned earlier, all user packages are compiled to work directory by default.

An example on how to use the above package in your design: ( test.vhd )

--An example for a module using package..
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--note this line.The package is compiled to this directory by default.
--so don't forget to include this directory.
library work;
--this line also is a must. This includes the particular package into your program.
use work.test_pkg.all;

--Note, how we could use the custom data type in your entity as input and output.
entity test is
port (clk : in std_logic;
      a1 : in t1;
      b1 : in t1;
      c1: out t1
    );
end test;

architecture Behavioral of test is

begin

process(clk)
begin
    if(rising_edge(clk)) then
    --for doing xor operation at every positive edge of clock cycle.
    --Note how we used a custom function here.
        c1 <= xored(a1,b1);   
    end if;
end process;

end Behavioral;

Another advantage of using a package is that, by simply editing the data types or functions in the package body, you can alter the design specifications up to a level. For example, in the above design, changing the record type t1 wouldn't need any editing to be done in the test.vhd file at all. 

Packages help designers to modularize their designs more effectively.