Deceptive DDS Tables

The direct digital synthesizer has become a fundamental block in many FPGA based designs.  It is an interesting project to work on, but realistically there isn’t much need to re-write a DDS if you have access to IP from an FPGA vendor.  This article comes about from looking at one of the most basic DDS optimizations — compressing the sine ROM by using only the first quadrant of the sine wave.

The DDS’s classical use is for generating sine waves.  The ability to get a matched sine/cosine output with correct amplitude/phase relationships makes the DDS ideal for a LO in a software defined radio.  The parallelism makes it ideal for very-high rate systems as well.

The primary feature of the DDS is the sine table.  There are a variety of options for the sine table and for compression of the sine table.  The most common choice is for a table that stores “the first 90degrees” of a sine wave.  This is because the sine wave has symmetry on the downward slope, and also is symmetric in terms of the excursion above/below 0.

A book that I had on frequency synthesis (PLL/DDS) had an interesting passage.  They suggested storing the results of sin(\frac{\pi n}{2^{b+1}}) \mid n = [0,2^b) .  At this point, the two MSB’s of the phase accumulator could be used to conditionally invert the address lines, as well as conditionally negate the output of the table.

The text described explicitly what this meant — that LUT address started at 0000, increased to 1111, then went from 1111 to 0000, then from 0000 to 1111 (output negated), then 1111 to 0000 (output negated).  It sounds very nice, and probably works well enough for very large tables.  But what about a simple 16 point table, something I could actually draw out on paper or make a nice graphic of?  It very quickly became apparent that the method wasn’t 100% correct.

The issue is that the 1111 entry, sin(\frac{\pi 15}{32}), is followed by the 1111 entry again.  It should be followed by sin(\frac{\pi 16}{32}).  The entry used is also sin(\frac{\pi 17}{32}) (due to symmetry), and is itself followed by sin(\frac{\pi 18}{32}).  The entire downward portion is slightly early.  This continues until the 0000 entry is encountered.  The next sample is a repeat of the 0000 entry.  This is also clearly wrong — the sine wave should be changing rapidly at the 0 crossing, not slowly!

The listed method would work if the tabled was generated using sin(\frac{\pi (2n+1)}{2^{b+1}}) \mid n = [0,2^b)  This has the effect of shifting the table entries though.  Now neither 0d nor 90d are in the table, but now repeating the 1111 table entry twice is correct, as is repeating the 0000 entry with the 0000 entry negated.

There are a handful of nuances to this table generation.  For example, sin(0) can be exactly represented, while the offset table doesn’t really have this as a “nice” value.  Also, it is nice to have a phase of zero for a phase accumulator of zero.

Another choice would be to simply store the non-offset values in the table, then modify the address to pick up the correct values, other than special cases for 0d, 90d, 180d, and 270d.  This can be done using the negation, a(-x), instead of the inversion, a(~x), of the address lines.  The addressing would progress 00,01,10,11,00,11,10,01, …  Addresses of all 0’s would then be special cases to select the 0, +1, or -1 constant as appropriate.  Special cases are a bit annoying though — they need extra testing to ensure that they actually work.

Linear interpolation is sometimes used to get a better approximation of the sine wave.  Using the offset values in the sine wave table can correct for the lack of a 0d/180d location.  This method can be used to initialize the phase accumulator to a negative half step bias, thus correcting for the table’s offset.  Even without interpolation, the negative half-step bias can be added to the phase accumulator, the value of the output just won’t be zero.  (See the second post for an explanation for  why this might be a good thing.)

In the end, I found it interesting that multiple books and thesis describe the DDS basics without addressing this issue in table generation.  I suspect it is because it is very easy to overlook for 1024+ entry tables.  The result should mostly be an unexpected PM signal, which might be attributed to issues with quantization.

This entry was posted in FPGA, Math. Bookmark the permalink.

Comments are closed.