Dithering adds harmonic distortion after filtering and upsampling I have an issue which I wasn't able to solve for a few days. I have implemented a polyphase FIR interpolation filter within an FPGA which does interpolate by a factor of 8 or 16 times. I do accept an audio word of up to 32 bits and coefficients are quantized on 35 bits resulting in MAC units of 32x35 and an accumulator with width of 67 bits.

The filter itself works flawlessy. There aren't any issues in general. However, I did add a dithering algorithm which uses LFSR to generate a random number two times in a row which is then added to the output from the filter (67 bits wide) in order to reduce bit-depth from 67 bits to lets say 18 bits. LFSR does generate a uniform distribution, so adding two pseudorandom numbers generated by it results in a triangular distribution (TPDF dithering).

For the sake of simplicity I did implement following equation within VHDL:

output_lch_sample_dithered_int &lt;= std_logic_vector(signed(output_fifo_lch_sample_int) + signed('1' &amp; dither_rnd1_int(47 downto 0)) + signed('0' &amp; dither_rnd0_int(47 downto 0)));
output_rch_sample_dithered_int &lt;= std_logic_vector(signed(output_fifo_rch_sample_int) + signed('1' &amp; dither_rnd1_int(47 downto 0)) + signed('0' &amp; dither_rnd0_int(47 downto 0)));


It has a forced sign, so hopefully the triangular distribution is around 0 (from -1 to 1 by simplifying the idea). Keep in mind that lch_int has 67 bits. Basically dither_rnd0_int is the first generated output from LFSR and dither_rnd1_int is a previous dither_rnd0_int which is replaced with a new value each tick (the same clock is used for both). I have tried different ways of summing random values (signed, unsigned, addition, subtraction) to match the generic requirement it all does result in the same issue.

As soon as I add those pseudorandom values generated by the LFSR to low leveled signal like -96 dBFS several harmonic distortion (mostly 2nd, 3rd, 4th and 5th) do show up. This is not happening with signal -60 dBFS and above. In fact, there are no changes at all within higher signals (no distortion changes and no more noise).

Following are two measurements of a -96 dBFS signal with and without dithering to visualize the issue:

Without dithering:



With dithering:



The most interesting part is that a signal of level -70 dBFS does in fact gain a bit from using the same dithering:

Without dithering:



With dithering:



Somewhere around -75 dBFS is where dithering changes nothing and the lower we go it gets worse with dithering since it does bring strange harmonic distortions.

It's an old R-2R 18-bit DAC from late 80 / early 90, so don't mind the generic THD, just the difference.

The question is where do those extra harmonic distortions come from? Are they hiding within LSBs which I'm truncating and I do not see them unless I enable dithering which simply reveals them? If so, why would there be there? How can FIR coefficients introduce harmonic distortions? Considering the fact that I do quantize them on 35 bits with rounding I would not expect any issues above -150 dBFS or so.

This issue happens only if dithering is done AFTER filtering. If I dither input signal (before filtering and upsampling) the results are correct and quantization error is transformed into random noise. I did try to implement different coefficients with different windowing methods and types (linear or minimum), but the result is always the same.
This issue happens only when I want to reduce bit depth of filter output (67 bits) to 18 bits and only at low leveled signals. If I add dither to an input word of 32 bits effectively reducing it to 18 bits I do get correct results.
It seems like this is an expected behavior because -96 dBFS signals are too much correlated with the quanitzation error due to 18 bit trancation.

Adding more dither, like on 2 LSB does in fact get rid of those extra harmonics. However, the noise floor is getting much larger than expected. I don't think it's possible to have the best SNR in audible range while shaping the noise away to the end of the spectrum. I've tried that with 2nd noise shaper (TDPF dither with high-pass function) and it did not give any good results.