Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

Trigonometric functions and arithmetic in VHDL

Status
Not open for further replies.

kureigu

Member level 2
Joined
Jan 14, 2013
Messages
49
Helped
0
Reputation
0
Reaction score
0
Trophy points
1,286
Location
Scotland
Activity points
1,779
I'm still relatively new to VHDL and I'm having trouble working out how to implement trigonometric functions.
I need to implement the following function in VHDL to determine the angle from an inclinometer, and any advice on how to do this would be much appreciated.

Code:
alpha = arcsin( (Vout-Offset)/0.035 )

[Offset = 2.5v, Vout = 0 to 5v]

Converted to deal with change in bits, rather than voltage:
Code:
alpha = arcsin( (Data-Offset)/14.336 )

[Offset = 0x3FF, Vout = 0 to 0x7FF]


The result that I get out of it preferably needs to be in degrees with a resolution of 0.1 degree. Arguably it could be scaled by a factor of 10 so that, for example, 39.4deg becomes 394. I'd like to display it on a 4x7seg display at some point too.
The value that I need to feed into the function is an 11bit unsigned value from an ADC at present.

What would I have to do to implement such a function with the accuracy I require? Would I need to know how to use floating point too or can I manage with fixed point?
I should stress that my VHDL experience so far has only really covered digital interfacing (SPI, I2C), driving some 7 segment displays, and straightforward arithmetic functions and assignments.
 

A conversion table is an appropriate solution for the problem. According to the low ADC resolution, a direct table should be feasible. For larger resolutions > 12 bit, table interpolations can save ROM resources at the cost of additional multiply and add operations.

I prefer to calculate the table values in VHDL, but I don't know if your VHDL knowledge level suggests this solution.
 

So I'd essentially I could just have component called 'arcsin_convert' or something, that would contain an array with 0x7FF entries, each holding the result of the equation using that number?

How would I go about calculating the array values in VHDL? I could just use matlab to spew out the 0x7FF results I need, but I'd rather be able to tweak it with variables in code.
 

The table will be usual implemented as FPGA internal ROM, which is actually an initialized RAM. You can write a function that supplies the initialization values. It's "executed" at compile time rather than synthesized and therefore can use IEEE.math_real, e.g. real data type and trigonometric functions.
 
I've done this kind of thing in C and PHP before, but I'm not sure if it'd be the same approach in VHDL. Would you just have a while loop counting up, calculate the output, and then put it in an array with each iteration?
Do you think you could give a quick example of how to go about this? even with a very simple equation or something.


I guess this is how i picture it working at the moment. No idea if it would or not.
Code:
architecture behavioural of angle_lookup is

    signal count: unsigned(10 downto 0) := 0;

    my_array:       ;    -- I don't know how to intialise an array...

begin

    while (count < 2047) loop

        my_array(count) <= count*2; -- multiply by two - example function
        count <= count +1

    end loop;

    value_out <= my_array(data_in);

end behavioural;
 

Here's a table initialization using a generate statement.

Code:
CONSTANT ROMSIZE  : INTEGER  := 2**(N_ADDR-1);
CONSTANT ROMMAX	  : INTEGER  := 2**(N_WSIZE)-1;
TYPE SINTAB IS ARRAY(0 TO ROMSIZE-1) OF SIGNED (N_WSIZE-1 DOWNTO 0);
SIGNAL SINROM: SINTAB;

BEGIN

GENROM:
FOR idx in 0 TO ROMSIZE-1 GENERATE
  CONSTANT x: REAL := SIN(real(idx)*2.0*MATH_PI/real(ROMSIZE));
  CONSTANT xn: SIGNED(N_WSIZE-1 DOWNTO 0) := TO_SIGNED(INTEGER(x*real(ROMMAX)),N_WSIZE);
BEGIN
  SINROM(idx) <= xn;	
END GENERATE;
 
Thanks, I found this in one of your posts in the altera forum actually. Very helpful though, I'll be testing it out this morning.

Edit:
Ran into some trouble getting the code below to compile. Also, some 'x' values are going to be ±infinity, how will it respond to this?

My main errors from this are:
Code:
Error(29) Unknown identifier "to_unsigned"
Warning(33) Length of expected is 11; length of actual is 2048.

Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.math_real.all;

entity incl_lookup is
	
	--port();
end entity;

architecture lookup of incl_lookup is

	constant ROMSIZE: integer := 2048;				-- length of the ROM
	constant ROMWIDTH: integer := 11;				-- Width of the data addresses
	constant V_FACTOR: real := 14.113;	-- scale factor based on sensitivity of 35mV/deg
		
	type ROM is array(0 to ROMSIZE-1) of unsigned(ROMWIDTH-1 downto 0);
	signal NL_correct: ROM;

begin
	
	GENROM:
	for i in 0 to ROMSIZE-1 generate
	
		-----------------   asin(  data - 1023 / ~14.2 ) * pi/180
		constant x: real := ARCSIN( ( ( (real(i) - real(ROMSIZE/2)-1.0) / V_FACTOR) ) * MATH_DEG_TO_RAD );
		constant xn: unsigned(ROMSIZE-1 downto 0) := to_unsigned( round(x * MATH_RAD_TO_DEG * 10.0) + 90, ROMWIDTH);
	
	begin
	
		NL_correct(i) <= xn;

	end generate;
	

end lookup;
 
Last edited:

remove the non-standard std_logic_arith package and use numeric_std instead.

And the warning comes bacause you have made xn the same with as the number of entries in the table (ROMSIZE), you need to make it the width of a single entry (ROMWIDTH).
 

And the warning comes bacause you have made xn the same with as the number of entries in the table (ROMSIZE), you need to make it the width of a single entry (ROMWIDTH).

Ah, didn't see that. Thanks.

I've tried removing the std_logic_arith after much googling and discovering that it's not standard, but now I get a new error.

Code:
Illegal type conversion from std.STANDARD.REAL to ieee.NUMERIC_STD.UNSIGNED (numeric to array)
in line:
Code:
constant xn: unsigned(ROMWIDTH-1 downto 0) := unsigned( round(x * MATH_RAD_TO_DEG * 10.0));

Edit: fixed it by replacing that line with this:
Code:
constant xn: unsigned(ROMWIDTH-1 downto 0) := to_unsigned( integer( round(x * MATH_RAD_TO_DEG * 10.0), ROMWIDTH ) );

Would I even need to do the round or is that something that the conversion to an integer would handle anyway?
 
Last edited:

Would I even need to do the round or is that something that the conversion to an integer would handle anyway?

No, because, like you suggest, the type conversion to integer from real will do rounding to nearest for you.

- - - Updated - - -

Dont forget you need a length argument (ROMWIDTH) in the to_unsigned function.
 

Unfortuantely now I'm getting a lot of these such errors when I try to simulate it..

Code:
# ** Error: ABS(X) > 1.0 in ARCSIN(X)
#    Time: 0 ps  Iteration: 0  Region: /incl_lookup/GENROM(0) File: C:/Documents and Settings/user/My Documents/Dropbox/FPGA things/Learnings/Inclinometer_LUT/inlc_lookup.vhd
# ** Error: (vsim-86) Arg Value -726 is out of NATURAL range 0 to 2147483647.
#    Time: 0 ps  Iteration: 0  Region: /incl_lookup/GENROM(0) File: C:/Documents and Settings/user/My Documents/Dropbox/FPGA things/Learnings/Inclinometer_LUT/inlc_lookup.vhd
# ** Error: ABS(X) > 1.0 in ARCSIN(X)
#    Time: 0 ps  Iteration: 0  Region: /incl_lookup/GENROM(1) File: C:/Documents and Settings/user/My Documents/Dropbox/FPGA things/Learnings/Inclinometer_LUT/inlc_lookup.vhd
# ** Error: (vsim-86) Arg Value -726 is out of NATURAL range 0 to 2147483647.

....etc

It's strange because the function ARCSIN defined in ieee.math_real can take real numbers.

Also, for an inpuit of less than 202 or greater than 1844, I know it's going to return plus/minus infinity. How can I set specific values for these 'out of range' areas?
 

I can indeed!

Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

entity incl_lookup is
	
	port(clk: in std_logic;
	     data_in : in unsigned(11 downto 0);
	     data_out: out unsigned(11 downto 0)
	     );
end entity;

architecture lookup of incl_lookup is

	constant ROMSIZE: integer := 2048;				-- length of the ROM
	constant ROMWIDTH: integer := 12;				-- Width of the data addresses
	constant V_FACTOR: real := 14.113;	-- scale factor based on sensitivity of 35mV/deg
		
	type ROM is array(0 to ROMSIZE-1) of unsigned(ROMWIDTH-1 downto 0);
	signal NL_func: ROM;

begin
	
	GENROM:
	for i in 0 to ROMSIZE-1 generate
	
		-----------------   asin(  data - 1023 / ~14.2 ) * pi/180
		constant x: real := ARCSIN( ( (real(i) - real(ROMSIZE/2)-1.0) / V_FACTOR)  * MATH_DEG_TO_RAD );
		constant xn: unsigned(ROMWIDTH-1 downto 0) := to_unsigned( integer((x * MATH_RAD_TO_DEG * 10.0) + 90.0), 12);
	
	begin
	
		NL_func(i) <= xn;

	end generate;
	

  process(clk)
  begin
    
    if rising_edge(clk) then
      data_out <= NL_func( to_integer(data_in) );
    end if;
    
  end process;

end lookup;
 

The arcsin function needs a value of X <= 1. (you are input some rather large numbers)

So assuming this table covers the region 0-Pi (because -Pi-0 is just the value of the table negated), the value in your arcsin need to be:

constant x : real := ARCSIN( real(i)* 1.0 / real( ROMSIZE ) ) * MATH_RAD_TO_DEG;

- - - Updated - - -

Plus, you need to put ROMWIDTH as the size argument for the XN constant, otherwise it will error whenever ROMWIDTH is anything other than 12.
 

If you look at my equation and the value that arcsin is given, they are always between -1 and +1 (except for i<220 and i>1844 which I mentioned).

The equation has that strange value constant because it correlates to the sensitivity of an inclinometer. This whole LUT is for linearising the output of that sensor, so it's important that it take in the 11 bits from the ADC and convert that to an angle.

Also, are you suggesting I only have the lookup table for the 0 to Pi/2 section and determine when to invert it depending on whether the data input falls into the +ve or -ve (0-3FF or 400-7FF)?
My original plan was to have the whole thing and just add Pi/2 before storing it in the LUT so it would all be positive and could be stored unsigned values.

For visual reference, here's a graph of the arcsin function:
graph_arcsin.gif
 

Sorry, I didnt look at the parenthesis properly. What you'll need to do is write your own wrapper function to catch those out of range values so you dont call the arcsin function with an invalid value. Personally I would define the whole table as a constant and set it all in one initialization function, rather than use a signal and set it in a generate loop - but that is just my preference. A signal is usually used for a RAM, and seeing a constant would tell me it is a ROM.

Because there is symmetry in the table, use that to your advantage and only use half the ram required with the trade off of a slight logic increase.
 

I was initially going to just hard code in an array with values generated elsewhere, but this seems like a more convenient to go down at the time... how wrong I was!

What you'll need to do is write your own wrapper function to catch those out of range values so you dont call the arcsin function with an invalid value.

I really would have no idea how I'd go about this. I guess my VHDL abilities are still a little too limited to realised this idea in it's fullest form for now!

- - - Updated - - -

I was initially going to just hard code in an array with values generated elsewhere, but this seems like a more convenient to go down at the time... how wrong I was!

What you'll need to do is write your own wrapper function to catch those out of range values so you dont call the arcsin function with an invalid value.

I really would have no idea how I'd go about this. I guess my VHDL abilities are still a little too limited to realised this idea in it's fullest form for now!
 

To write an initialisiaton function, just do something like this:


Code VHDL - [expand]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type ROM is array(0 to ROMSIZE-1) of unsigned(ROMWIDTH-1 downto 0);
 
function init_rom return ROM is
  variable ret : ROM;
begin
  for i in 0 to ROMSIZE-1 loop
    if i < 202 or i > 1800 then  --catch the illegal values
      ret(i) := SOME_VALUE;
    else
      ret(i) := YOUR_CALCULATION;
    end if;
  end loop;
 
  return ret;
end function init_rom;
 
constant ARCSIN_ROM : ROM := init_rom;

 

I was initially going to just hard code in an array with values generated elsewhere, but this seems like a more convenient to go down at the time... how wrong I was!

I don't think that the method is wrong. But if you attempt to calculate the table values by a mathematical expressions, you have to accepts the rules of mathematic. E.g. if you say all values are unsigned, there must be no negative numbers. "Value x is out of NATURAL range"

At first sight, I expect the same problems when calculating the values in a spreadsheet calculator.
 

hi all
i am new to this blog.
and i try to perform same trignometric function in xilinx13.2
but i got error
ERROR: The Top module has not been specified. This can happen if no sources have been added to the project,
will you please help out me what i am missing ....
thanks and regards
grajput
 

Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top