Search code examples
vhdl

VHDL: Generate a generic case statement with adjustable amount of cases


I want an approximation of the Tanh function by saving the values in a LUT (by this I am doing a quantization). I want to choose the Number of entries in the LUT.

As an not-correct example, I imagine a code like

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use ieee.fixed_pkg.all;

entity tanh_lut is
    generic (
        MIN_RANGE: real := 0.0; -- Minimum value of x
        MAX_RANGE: real := 5.0; -- Maximum value of x
        DATA_RANGE_int: positive:= 8; 
        DATA_RANGE_frac: positive:= 8; 
    );
    Port ( DIN : in sfixed(DATA_RANGE_int-1 downto -(DATA_RANGE_frac-1));
           DOUT : out sfixed(DATA_RANGE_int-1 downto -(DATA_RANGE_frac-1))
end tanh_lut;

architecture Behavioral of tanh_lut is
begin
 

    lut_gen: for i in 0 to LUT_SIZE-1 generate

        constant x_val :      real := MIN_RANGE + (MAX_RANGE - MIN_RANGE) *  i    / (LUT_SIZE-1);
        constant x_val_next : real := MIN_RANGE + (MAX_RANGE - MIN_RANGE) * (i+1) / (LUT_SIZE-1);
        constant y_val :      real := tanh(x_val);
        if DIN>=x_val_previous AND DIN<x_val then
                  DOUT  <= to_sfixed(tanh(y_val),DOUT  ) ;
        END IF
    end generate;
end Behavioral;

Per example, if I want 4 entries in the range 0 to 3, I want that it is synthesizing a code like:

        if DIN>0 AND DIN<=1 then
                  DOUT  <= to_sfixed(0, DOUT);
        else DIN>1 AND DIN<=2 then
                  DOUT  <= to_sfixed(0.76159415595, DOUT);
        else DIN>2 AND DIN<=3 then
                  DOUT  <= to_sfixed(0.96402758007, DOUT);
        else DIN>3 AND DIN<=4 then
                  DOUT  <= to_sfixed(0.99505475368, DOUT);
       End if

Is there any way that a code like this or a code which implements the idea behind this is possible?

A simple LUT with addresses is not possible because the addresses are always integer and DIN is fixed point, e.g., 1.5

The other possibility would be two LUTs, one for mapping the Input to an address, another for mapping the address to the LUT entry, e.g., LUT1: 1.5=> address 5, LUT2: address 5 => 0.90. But by this I would double the amount of resources what I dont want

My requirements: things like the tanh(x) should not be synthesized, only the final value of tanh(x). It shoudl also be hardware efficient


Solution

  • It does not matter if you use a nested „if-elsif“ construct or if you use a new „if“ construct for each check. So you can create a loop like this:

    for i in 0 to c_number_of_checks-1 loop
       if c_boundaries(i)<DIN and DIN<=c_boundaries(i+1) then
           DOUT <= c_output_values(i);
      end if;
    end loop;
    

    Of course you must provide the constants c_number_of_checks and c_boundaries, c_output_values. This can be done by:

    constant c_number_of_checks : natural := 4;
    type array_of_your_data_type is array (natural range <>) of your_data_type;
    constant c_boundaries : array_of_your_data_type(c_number_of_checks downto 0) := init_c_boundaries(c_number_of_checks);
    constant c_output_values : array_of_your_data_type(c_number_of_checks-1 downto 0) := init_c_output_values(c_number_of_checks);
    

    This means you will need the functions init_c_boundaries, init_c_output_values, which create arrays of values, which can initialize the constant c_boundaries and c_output_values. But this is not complicated (you can use from ieee.math_real the function TANH), as the functions need not to be synthesizable, as they are called only during compile time.

    As you see, you will have some effort. So perhaps it is easier to follow the other suggestions. If you do so (value as address of a LUT) you should think about automatic ROM inference, which is provided by several tool chains and will give you a very efficient (small) hardware.