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

Monday, December 7, 2020

Synthesizable Polynomial Equation Calculator in VHDL

     A polynomial equation is an equation which is formed with variables, coefficients and exponents. They can be written in the following format:

                        y =anxn + an-1xn-1 + ... +a1x + a0 .

Here an, an-1  ... , a1 ,a0  are coefficients,

n, n-1, ... etc are exponents and x is the variable. 

In this post, I want to share, VHDL code for computing the value of 'y' if you know the value of 'x' and coefficients forming the polynomial. 

Directly calculating the polynomial with the above equation is very inefficient. So, first we reformat the equation as per Horner's rule:

                        y =(((anx + an-1)x + an-2)x + an-3)x + ... )x +a0 .

This modified equation can be represented by the following block diagram:





There are 3 sub-entities in the above block diagram.

1) Block number 1:

    This block keep generating successive powers of input x. 

In the first clock cycle it generates x^0 = 1.

In the 2ndclock cycle it generates x^1.

In the 3rd clock cycle it generates x^2.

In the nth clock cycle it generates x^(n-1).

    Every clock cycle, the previous outputs are multiplied with 'x' to get the next power of x.

2)Block number 2:

    The powers of x generated in the first block are multiplied with the corresponding coefficients of the polynomial in Block numbered 2. The coefficients are declared and initialized in the top level entity and can be changed easily.

3)Block number 4:

    This block is basically an accumulative adder which accumulates all the products generated by the multiplier. Once it does 'n' additions, we have the final result available in output 'y'. An output 'done' is set as high to indicate that the output is ready.


Let me share the VHDL codes for these entities along with the testbench.

1) power_module.vhd


--This module generates successive powers of input x in each clock cycle.
--For example 1,x,x^2,x^3,x^4 etc. 
--The output from the previous cycle is multiplied again by x to get the next power.
--The output from this entity is passed to the multiplier module, where it gets
--multiplied by the corresponding coefficient.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity power_module is
    generic(N : integer := 32);
    port (
        Clock : in std_logic;
        reset : in std_logic;
        x : in signed(N-1 downto 0);
        x_powered : out signed(N-1 downto 0)
    );
end power_module;

architecture Behav of power_module is

    signal x_pow : signed(N-1 downto 0) := to_signed(1,N);

begin

    POWER_PROC : process(Clock,reset)
        variable temp_prod : signed(2*N-1 downto 0);
    begin
        if (reset='1'then
            x_pow <= to_signed(1,N);
        elsif(rising_edge(Clock)) then
            temp_prod := x*x_pow;
            --The MSB half of the result is ignored. We assume that all intermediate powers of x
            --can be represented by a max of N bits.
            x_pow <= temp_prod(N-1 downto 0);
        end if;
    end process;

    x_powered <= x_pow;

end architecture;


2) multiplier.vhd


--The successive powers of x are multiplied by the corresponding coeffcients here. 
--The results are sent to the adder module which acts as an "accumulator".
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity multiplier is
    generic(N : integer := 32);
    port (
        Clock : in std_logic;
        x,y : in signed(N-1 downto 0);
        prod : out signed(N-1 downto 0)
    );
end multiplier;

architecture Behav of multiplier is

begin

    MULT_PROC : process(Clock)
        variable temp_prod : signed(2*N-1 downto 0) := (others => '0');
    begin
        if(rising_edge(Clock)) then
            temp_prod := x*y;
            --The MSB half of the result is ignored. We assume that all intermediate numbers
            --be represented by a max of N bits.
            prod <= temp_prod(N-1 downto 0);
        end if;    
    end process;
    

end architecture;


3) adder.vhd


--The output from multiplier module is received by this module and accumulated to
--form the output. If the polynomial equation has a degree of 'n' then 'n' additions has to take place
--to get the final result.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity adder is
    generic(N : integer := 32);
    port (
        Clock : in std_logic;
        reset : in std_logic;
        x : in signed(N-1 downto 0);
        sum : out signed(N-1 downto 0)
    );
end adder;

architecture Behav of adder is

    signal temp_sum : signed(N-1 downto 0) := (others => '0');

begin

    ADDER_PROC : process(Clock,reset)
    begin
        if (reset = '1'then
            temp_sum <= (others => '0');
        elsif(rising_edge(Clock)) then
            temp_sum <= temp_sum + x;
        end if;
    end process;

    sum <= temp_sum;

end architecture;


4) Top Level Entity : poly_eq_calc.vhd


--This is the top level entity for the polynomial equation calculator.
--The power_module, multiplier and adders entities are instantiated inside this top level block.
--When the output signal done is High, the output available at the 'y' output is the result we want.
--Ignore all other values of 'y' when done is Low.
--We have assumed that all the intermediate numbers calculated to reach the final result, can be represented
--by a maximum of N bits. This simplifies the design very much.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity poly_eq_calc is
    generic(N : integer := 32);
    port (
        Clock : in std_logic;
        reset : in std_logic;
        x : in signed(N-1 downto 0);
        y : out signed(N-1 downto 0);
        done : out std_logic
    );
end poly_eq_calc;

architecture Behav of poly_eq_calc is

    component power_module is
        generic(N : integer := 32);
        port (
            Clock : in std_logic;
            reset : in std_logic;
            x : in signed(N-1 downto 0);
            x_powered : out signed(N-1 downto 0)
        );
    end component;

    component multiplier is
        generic(N : integer := 32);
        port (
            Clock : in std_logic;
            x,y : in signed(N-1 downto 0);
            prod : out signed(N-1 downto 0)
        );
    end component;

    component adder is
        generic(N : integer := 32);
        port (
            Clock : in std_logic;
            reset : in std_logic;
            x : in signed(N-1 downto 0);
            sum : out signed(N-1 downto 0)
        );
    end component;

    signal x_powered,coeff,product_from_mult : signed(N-1 downto 0) := (others => '0');
    constant NUM_COEFFS : integer := 4--Change here to change the degree of the polynomial. 
    type arr_type is array (0 to  NUM_COEFFS-1of signed(N-1 downto 0);  
     --Eq : 3*x^3 - 2*x^2 - 4*x + 5;
    --The coefficients belonging to higher powers of x are stored in higher addresses in Coeffs array.
    signal Coeffs : arr_type := (to_signed(5,N),  --change coefficients here. 
                                to_signed(-4,N),
                                to_signed(-2,N),
                                to_signed(3,N));
 
    signal reset_adder :  std_logic := '0';

begin

    --Instantiate the 3 sub-entities.    
    calc_power_of_x : power_module generic map(N => N)
        port map(Clock,reset,x,x_powered);

    multiply : multiplier  generic map(N => N)
        port map(Clock,x_powered,coeff,product_from_mult);

    add : adder  generic map(N => N)
        port map(Clock,reset_adder,product_from_mult,y);

    --The process controlling the 3 sub-entities and also supplying them with coefficients.    
    MAIN_PROC : process(Clock,reset)
        variable coeff_index : integer := 0;  
    begin
        if(reset = '1'then
            done <= '0';
            coeff_index := 0;
            coeff <= Coeffs(0);
            reset_adder <= '1';
        elsif(rising_edge(Clock)) then
            reset_adder <= '0'--the disabling of 'reset of adder' gets noticed by adder entity in the next clock cycle.
            if(coeff_index < NUM_COEFFS-1then
                coeff_index := coeff_index+1;
                coeff <= Coeffs(coeff_index);   --send the coefficients one by one to the multiplier entity. 
            elsif(coeff_index = NUM_COEFFS-1then
                coeff_index := coeff_index+1;
            else
                done <= '1';    --The final result is available in 'y' now.
            end if;    
        end if;
    end process;

end architecture;


5) Testbench : tb_poly_eq_calc.vhd


--Testbench code.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

--The testbench entity is always empty.
entity tb_poly_eq_calc is
end tb_poly_eq_calc;

architecture sim of tb_poly_eq_calc is

    constant clk_period : time := 10 ns--set the clock period here.

    signal Clk : std_logic := '1';
    signal reset : std_logic := '1';
    constant N : integer := 32--change the width of input and output here.
    signal x,y : signed(N-1 downto 0) := (others => '0');
    signal done : std_logic := '0';

begin

    Clk <= not Clk after clk_period / 2;    --generate clock

    --Instantiating Design Under Test.
    DUT : entity work.poly_eq_calc generic map(N => N)
        port map (Clk, reset, x, y, done);

    --Apply inputs here. Only 'x' can be changed here. 
    --To change coefficients and degree of polynomial edit the top level entity directly.
    SEQUENCER_PROC : process
    begin
        --first input.
        reset <= '1';
        wait for clk_period * 2.5;
        reset <= '0';
        x <= to_signed(4, N);
        wait for clk_period;
        wait until done='1';    --wait for result to be out.
        wait for clk_period;

        --second input.
        reset <= '1';
        wait for clk_period;
        reset <= '0';
        x <= to_signed(7, N);
        wait for clk_period;
        wait until done='1';   --wait for result to be out.
        wait for clk_period;

        --third input.
        reset <= '1';
        wait for clk_period;
        reset <= '0';
        x <= to_signed(15, N);
        wait for clk_period;
        wait until done='1';   --wait for result to be out.
        wait for clk_period;

        --fourth input.
        reset <= '1';
        wait for clk_period;
        reset <= '0';
        x <= to_signed(-9, N);
        wait for clk_period;
        wait until done='1';   --wait for result to be out.
        wait for clk_period;
        reset <= '1';

        wait;
    end process;

    --Check if the results are correct and report the result.
    CHECK_RESULTS_PROC : process(Clk)
        variable actual_res : integer := 0;
        variable input_num,x_int : integer := 0;
    begin
        if(rising_edge(Clk)) then
            if(done = '1'then
                x_int := to_integer(x);
                --Change this equation if you change the polynomial eq inside the top level entity.
                actual_res := 3*x_int*x_int*x_int - 2*x_int*x_int - 4*x_int + 5;  
                input_num := input_num+1;
                if(actual_res = to_integer(y)) then
                    report "Input number "  & integer'image(input_num) & " Worked Well";
                else
                    report "Input number "  & integer'image(input_num) & " Has Error"
                end if;   
            end if;                   
        end if;
    end process;

end architecture;


The code was simulated successfully using Modelsim 10.4a version. The simulation waveform below shows the signals for the first two set of inputs:



The design was synthesized successfully using Vivado 2020.2 version. 


Saturday, December 5, 2020

Generic VHDL Code for Binary to Gray and Gray to Binary converter

    Few years back I had written a 4 bit converter for conversion between Gray and Binary codes. After receiving much positive response I decided to write a generic version of the same.

Let me share the codes...

Binary to Gray Code Converter:


LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

entity bin2gray is
    generic(N : integer := 4);
port(   bin : in std_logic_vector(N-1 downto 0);  --binary input
        G : out std_logic_vector(N-1 downto 0)  --gray code output
        );
end bin2gray;

architecture gate_level of bin2gray is 

begin

G(N-1) <= bin(N-1);
--generate xor gates.
xor_gates : for i in N-2 downto 0 generate
    G(i) <= bin(i+1xor bin(i);
end generate;    

end;


Gray Code to Binary Converter:


LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

entity gray2bin is
    generic(N : integer := 4);
port(   G : in std_logic_vector(N-1 downto 0);    --gray code input
        bin : out std_logic_vector(N-1 downto 0)  --binary output
        );
end gray2bin;

architecture gate_level of gray2bin is 

signal temp : std_logic_vector(N-1 downto 0);

begin

temp(N-1) <= G(N-1);
--generate xor gates.
xor_gates : for i in N-2 downto 0 generate
    temp(i) <= temp(i+1xor G(i);
end generate;    

bin <= temp;

end;

Testbench:


library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
entity tb is
end tb;
 
architecture behavior of tb is 
 
    -- component declaration for the unit under test's (uut) 
component bin2gray is
    generic(N : integer := 4);
port(   bin : in std_logic_vector(N-1 downto 0);  --binary input
        G : out std_logic_vector(N-1 downto 0)  --gray code output
        );
end component;

component gray2bin is
    generic(N : integer := 4);
port(   G : in std_logic_vector(N-1 downto 0);    --gray code input
        bin : out std_logic_vector(N-1 downto 0)  --binary output
        );
end component;

constant N : integer := 16;  --Change this to control the number of bits in the input/output.
signal bin,g,bin_out : std_logic_vector(N-1 downto 0) := (others => '0');
signal error : integer := 0
begin
    -- Both the converters are connected back to back to see the binary input going to the
    --first entity is the same as the output coming out of the second entity.
   uut1: bin2gray generic map (N => N) port map (
          bin => bin,
          g => g
        );
 
   uut2: gray2bin generic map (N => N) port map (
          g => g,
          bin => bin_out
        );
          
   -- stimulus process
   --this tests for all the input combinations.
   stim_proc: process
   begin        
        for i in 0 to 2**N-1 loop   --loop through all the  available inputs 
            bin <= std_logic_vector(to_unsigned(i,N)); --convert integer to std_logic_vector.
            wait for 5 ns;
             --Count the number of errors. Should be zero at the end of simulation.
            if(bin /= bin_out) then 
                error <= error + 1;
            end if;
            wait for 5 ns;
        end loop;    
        wait;
   end process;

end;


The codes were tested using Modelsim 10.4a version. Simply change the value of the constant 'N' in the testbench to test for different sized converters.


Wednesday, March 10, 2010

GENERIC's in VHDL - Construction of parametrized components

THIS BLOG POST WAS UPDATED ON 06-03-2024!


      Generics are used to add customizable properties to VHDL designs. Here are some situations where you might want to use generics.

  1. Customizable Properties: An example of this would be when you want to design a RAM(Random Access Memory) or a FIFO(First In First Out) whose data width and maximum number of elements can be changed without rewriting the code in multiple places.
  2. Configuration Flexibility: As a code base goes through different stages in development, lets say simulation, synthesis, board level testing etc.., the developer might want to use different configurations to make the process easier. For example, the clock frequency used for functional simulation might be different from what is available in the fpga board. Instead of making multiple versions of the code, generics can be used to add multiple options in a single code base. 
  3. Platform Portability: There are different FPGA vendors and devices out there, each with their unique features. To create an efficient design, the developer might need to use specific features available in fpga's. But that means one would need to write several versions of codes to target multiple fpga's. Using generics would solve such a problem.
  4. Easier Hierarchical Design: Generic's can be passed down from top module to sub modules. This means that by changing specifications at the top level, you can make the changes reflect in lower-level modules as well.
    In essence, generics in VHDL allows you to create designs that are adaptable and reusable.

Example: piso.vhd

--Parallel in serial out shift register
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

--The top module has two instantiations- a 8 bit PISO(parallel in serial out) register and a 4 bit PISO.
--Without the use of generics these two components need a seperate .vhd  file.
--But I have used "generic" keyword to solve this problem.
entity piso is
    generic ( WIDTH : integer := 8 );   --default value is 8
    port(clk : in std_logic;
	 load : in std_logic;
	 in1 : in std_logic_vector(WIDTH-1 downto 0);  -- note how the size of 'in1' is not fixed.
         out1 : out std_logic ); end piso; architecture Behavioral of piso is -- note how the size of 'temp' is not fixed. signal temp: std_logic_vector (WIDTH-1 downto 0) := (others => '0'); --initialize to zero
begin

process(clk)
begin
    if (rising_edge(clk)) then
        if (load = '0') then  -- load the register. 'load' signal is active HIGH and synchronous.
            temp <= in1;
            out1 <= '0';
        else
            --shift the register elements and output a single bit.
            out1 <= temp(WIDTH-1);
            temp(WIDTH-1 downto 1) <= temp(WIDTH-2 downto 0);
        end if;
    end if;
end process;

end Behavioral;

    In the VHDL code above, we've left the size of the PISO (Parallel-In, Serial-Out) register open-ended. This flexibility allows us to easily adjust the size of the PISO register by simply modifying the value of the generic variable WIDTH. Remarkably, no other alterations in the code are necessary. 

    Let's delve into the testbench code to witness how this adaptability is implemented in practice.

Testbench - How do you instantiate a component with generic variables:


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

--Now the instantiation of this component in top module is shown below:
entity tb_piso is
end tb_piso;

architecture behavior of tb_piso is

component piso
    generic ( WIDTH : integer := 8 );   --default value is 7
    port(clk : in std_logic;  load : in std_logic; in1 : in std_logic_vector(WIDTH-1 downto 0); -- note how the size of 'in1' is not fixed.
out1 : out std_logic ); end component;
--signal declarations
signal in1 : std_logic_vector(7 downto 0):="10000110";
signal in2 : std_logic_vector(3 downto 0):="1001";
signal load1,load2,o1,o2 : std_logic :='0';
signal clk : std_logic := '0';

begin

--Note down the next two lines.
--This is how you pass generic parameters to the instantiated components.
--At compile time, the variable 'width' will be replaced everywhere by the values passed through here.
--Note that I used positional association for port mapping here. This is not recommended.
--Please use named association in your actual designs.
piso1 : piso generic map (WIDTH => 8) port map(clk,load1,in1,o1);
piso2 : piso generic map (WIDTH => 4) port map(clk,load2,in2,o2);
end behavior;

A very cool way to get things done, isn't it?

    Generics are perfectly synthesisable. During compilation, all generic variables are substituted with their assigned hard-coded values, ensuring smooth integration into the final design.