sine(x) lookup table. - Page 1
Forum section: electronics / general

author: hamster_nz
author_rank: Super Contributor
timestamp: June 11, 2017, 09:51:41 am
content: I'm making a sine(x) lookup table, for use in Direct Digital Synthesis or maybe SDR (for I/Q values), indexed by a phase accumulator. This is for in an XIlinx FPGA, so I want to use a single 1024 entry, 18-bit block RAM.My quandary:I don't know the proper term, but should I use "left edge" or "center" values in the table?e.g.int value = sin(i/1024.0*2*3.141592)*scale;vsint value = sin((i+0.5)/1024.0*2*3.141592)*scale;Initially I was going with the "left edge" style table, and to halve the memory needed I was going to store the first half, and invert the table to get the second half. That works just as well for either styleBut now I am thinking of holding only 1/4th the full table in a 1024 entry table, and then 'mirroring' for the 90 to to 180 degrees (and also the 270 to 360 degrees quadrant) - using each value four times over. However, this doesn't work with the "left edge" table, only the "center" style table.I am also thinking of adding interpolation between samples later on, as with the current table size the steps between values quite large - but that just make me want to go "left edge" again, because the math is easier / quicker at first glance, and the RAM blocks are dual-port after all...What does everybody else do? Is there an obvious "more correct" choice, that I'm just not seeing?Maybe this is the crux of my problem. I should round/truncate the phase accumulator differently depending on the style of table I am using, and how I am using it.

author: bktemp
author_rank: Super Contributor
timestamp: June 11, 2017, 10:05:34 am
content: I am no DSP expert, so maybe I am wrong.If you use left edge, the first value will be sin(0)=0. If you mirror the waveform, you will end up with 2 times sin(0) instead of +/- a small value for the first and the last value of the waveform period. That's why I used the center values. It looked more correct to me than using the left edge version.Shouldn't it be i/1024.0 instead of i/1048.0 for 1024 samples?

author: voltsandjolts
author_rank: Unknown
timestamp: June 11, 2017, 10:06:28 am
content: I don't have an answer for you but this is worth a read:https://www.eevblog.com/forum/microcontrollers/code-used-for-sine-table/

author: brucehoult
author_rank: Super Contributor
timestamp: June 11, 2017, 10:14:14 am
content: I don't think the question makes sense, conceptually."Left edge" or "center" implies that you're thinking about ranges of values. But what you have in the table is always individual points, regardless of whether or not you interpolate those points later.Linear interpolation can never give you a value that is more extreme than a value in your table, so I think it's important to have table entries as near to 0.0 => 0.0 and pi/2 => 1.0 as possible.If you're doing linear interpolation then it's better to have the pi/2 entry as perfect as possible (certainly make it contain exactly 1.0 as the result). Values near to zero will interpolate well anyway.

author: bktemp
author_rank: Super Contributor
timestamp: June 11, 2017, 10:28:12 am
content: Quote from: brucehoult on June 11, 2017, 10:14:14 amIn a fixedpoint implementation even beeing slightly off pi/2 will be good enough:sin(1023/1024*pi/2)=0.99999882345When using a 16bit output range even being 1 full sample off will still result in a value of +32766.96, rounded to +32767.Around pi/2 is the slowest part of the sinewave, that's why I think the most important point is around 0.

author: Kalvin
author_rank: Super Contributor
timestamp: June 11, 2017, 10:35:38 am
content: One solution is to use 4 SRAM banks for now for the full sine table and optimize later. Linear interpolation will be also easier when you have the full table at hand.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 11, 2017, 10:36:49 am
content: Quote from: bktemp on June 11, 2017, 10:05:34 amYes - agreed, but still confused somewhat.QuoteYes - good catch.

author: Kalvin
author_rank: Super Contributor
timestamp: June 11, 2017, 11:30:12 am
content: You may find this interesting:https://github.com/jorisvr/vhdl_sincos_gen

author: hamster_nz
author_rank: Super Contributor
timestamp: June 11, 2017, 11:44:05 am
content: Quote from: bktemp on June 11, 2017, 10:28:12 amBy averaging the sine value looked up for the current phase, with that 0.5 entries earlier, I've made a 1024 entry table look like 8192 entries, with a single bit of error in a signed 18-bit result. Poorly written C test program for what ends up being quite nice HDL:Code:#include <stdio.h>#include <math.h>const int scale = (1<<18);int table;int table_should_be;int table_lookup;/* Map one quadrant into four */int lookup(unsigned int i) {int value;int j = i & 2047;if(i & 1024)value = table[2047-(j & 2047)];elsevalue = table[j & 1023];if(i & 2048)value = -value;return value;}/* Test that they lookup ok */int main(int argc, char *argv[]){unsigned int i;for(i = 0; i < 1024; i++) {table = sin((i+0.5)/2048.0*3.141592)*scale;}for(i = 0; i < 8192; i++) {table_should_be = sin(i/4096.0*3.141592)*scale;table_lookup = (lookup((i-1)/2) + lookup(i/2))/2;}for(i = 0; i < 8192; i++) {printf("%8i %8i (%3i)\n",table_lookup,table_should_be, (table_lookup - table_should_be));}}

author: NorthGuy
author_rank: Super Contributor
timestamp: June 11, 2017, 01:37:43 pm
content: Edge-based is mathematically correct. It does work around the stitches.<edit>Disregard the following code. See my next post</edit>Code:if (x & 1024) {addr = x & 1023;}else {addr = (-x) & 1023;}sin = table;if (x & 2048) {sin = -sin;}Interpolation might not be a good idea for FPGA, because it requires two accesses to the table. So, it will be either:- two times slower. Probably not what you want.or- two instances of the table. In this case it is better to use a single table that is twice as big, which is more accurate.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 11, 2017, 02:06:48 pm
content: Sorry, the pseudo-code I posted certainly doesn't work. Here it is:Code:addr = x & 1023;if (x & 1024) {addr = 1024 - addr;}// The required table must be 1025 elements long.// Would be fine for CPU, but for FPGA, it's better to have// 1024 elements in BRAM and create a bypass for the extra elementif (addr & 1024) {sin = 1;}else {sin = table;}if (x & 2048) {sin = -sin;}

author: Kalvin
author_rank: Super Contributor
timestamp: June 11, 2017, 02:23:34 pm
content: If the spectrum purity and SNR is an issue, you may want to take a look at this paper "A study on look-up table based sine wave generation" which compares different methods of interpolation and the resulting sine wave output spectrum, including the resources consumed in FPGA:https://www.researchgate.net/publication/255531046_A_study_on_look-up_table_based_sine_wave_generation

author: hamster_nz
author_rank: Super Contributor
timestamp: June 11, 2017, 09:00:12 pm
content: Quote from: NorthGuy on June 11, 2017, 01:37:43 pmOn the fabric I'm using the Block RAM are dual-ported, so the second lookup costs next to nothing.

author: KE5FX
author_rank: Super Contributor
timestamp: June 11, 2017, 09:19:11 pm
content: You know there's a free DDS coregen, right?

author: CatalinaWOW
author_rank: Super Contributor
timestamp: June 11, 2017, 09:25:15 pm
content: Quote from: Kalvin on June 11, 2017, 02:23:34 pmThis hits the nail on the head. Whether left edge or center (or even right edge) is better depends on how you will use the results. The key is to make the same assumption at creation and use.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 11, 2017, 10:39:40 pm
content: Quote from: Kalvin on June 11, 2017, 02:23:34 pmHey thanks! Interesting paper.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 11, 2017, 11:18:36 pm
content: Quote from: KE5FX on June 11, 2017, 09:19:11 pmSure, but because this is a hobby it is more interesting to build my own things, for the learning along the way.