1
$\begingroup$

Breaking my brain all morning with this reading previous questions and googling ...

I have made an RRC filter from the equation on wikipedia. It works fine and I compared it to commpy library in python.

But what I dont understand is the frequency amplitudes.

  • Should the RRC filter have normalised gain, i.e. 0 dB by dividing the impulse response by sum(impulse response)?
  • Or should I be dividing the FFT result by fftsize?
  • Or should I infact be doing neither of these two?

I browse the internet and many images have lots of different gains, none of which I can recreate ... but I can recreate the impulse and general frequency shape. The frequency response is just not shifted vertically correct (in dB scale) to show same gain as other images. What am I missing :(

#Define all parameters needed for RRC
alpha = 0.35 
Fs = Fs
span = 10
sps = 10
num_taps = span*sps
Rs = Fs / sps

#taps must be odd for impulse to be symmetrical on time zero
if not num_taps % 2: num_taps += 1

#time vector
t = np.arange(-((num_taps-1)/2) , (((num_taps-1)/2)+1) , 1) * (1/Fs)

#Root Raised Cosine and deal with special cases
h = np.zeros(len(t))
for element in enumerate(t):
    i,x = element #i holds array index, x holds value of t
    if x == 0: 
        h[i] =  (1 - alpha + (4 * alpha / np.pi))

    elif (alpha != 0 and x == (1 / Rs) / (4 * alpha)) or (alpha != 0 and x == -(1 / Rs) / (4 * alpha)) :
        h[i] = alpha / np.sqrt(2) * ( ((1 + 2 / np.pi) * np.sin(np.pi / (4 * alpha))) + ((1 - 2 / np.pi) * np.cos(np.pi / (4 * alpha))))

    else: #not special cases
        h[i] = (np.sin(np.pi * x * Rs * (1 - alpha) ) + \
                4 * alpha * x * Rs  * np.cos(np.pi * x * Rs * (1 + alpha))) / \
                (np.pi * x * Rs * (1 - (4 * alpha * x *Rs) ** 2 ))

h = h # / np.sum(h) #Should I divide to normalise gain?

#Fourier Transform
fftsize = 4096
f = np.linspace(-Fs / 2, Fs / 2, fftsize) 

H = np.fft.fft(h,fftsize)  #Should I divide by fftsize here?!
H = 10*np.log10( np.abs(H)**2)
H = np.fft.fftshift(H)

#Make the plots
fig = plt.figure()
ax = fig.add_subplot(211)
ax.title.set_text('Raised Cosine Impulse Response')
ax.plot(t,h)
ax.grid(True)

ax = fig.add_subplot(212)
ax.title.set_text('Raised Cosine Frequency Response')
ax.plot(f,H)
ax.grid(True)
$\endgroup$
3
  • $\begingroup$ Is your filter being used as a pulse-shaping filter? $\endgroup$ – MBaz Feb 20 at 15:40
  • $\begingroup$ Hi MBaz. Yes - I thought that would be the only use of this type of filter? $\endgroup$ – Natalie Johnson Feb 20 at 15:54
  • $\begingroup$ Well, it could also be used as a low-pass filter. $\endgroup$ – MBaz Feb 20 at 16:00
2
$\begingroup$

The gain is completely arbitrary and you can scale it as desired for the overall receiver or transmitter design. Where special attention must be paid is with fixed point design where the best practice is to let the filters grow the signal- do not scale the coefficients or the input as that only introduces more quantization noise and degrades SNR. Let the filter grow (extended precision accumulators!) and then scale the output as you would do for any gain adjust to bring the signal level to within the range of the precision used.

Typical design practice for fixed point design which avoids precision errors is to use 2 more bits of quantization for the coefficients, sum the weighted taps in an extended precision accumulator and then scale the output to any level desired. In this case the quantization of the coefficients (if the same or less than the datapath) would cause the implementation to not meet the expected rejection and passband ripple given by the floating point equivalent design. Typically we want rejection to be greater than the quantization noise floor, and a reasonable rule-of-thumb is that the achievable rejection will be 5-6 dB/bit of coefficient quantization.

I detail this further at this post showing the quantization error contributions (such that you can also see if you want to limit the accumulator precision that you can scale after so many taps since the trade between quantization noise and accumulator precision is clearer, but in general the safest/easiest approach without further computation is to just wait until the out and let the accumulator be $log_2(N)+2$ higher in precision than the datapath where N is the number of taps in the filter.

Inter-filter bit width

This is further detailed here within the context of a general approach to FIR filter design:

what is the suitable design Method to the filter?

The graphics below demonstrate the quantization effects on a typical low pass FIR filter. This motivates the "best-practice" to use 2 more bits of quantization for the coefficients over what is used in the datapath, and the noise summation process in the filter motivates using extended precision accumulators and let the filter grow the signal (then truncate at the end) as detailed in the links above:

16 bit coefficients

8 bit coefficients

$\endgroup$
1
$\begingroup$

My recommendation is to normalize the filter impulse response to have energy equal to 1.

In continuous time, the digital communication system will transmit a symbol $a_i$ using the pulse $$s(t) = a_ip(t),$$ where $p(t)$ is an RRC pulse. The receiver will recover $a_i$ from $s(t)$ using a matched filter:

\begin{align} a_i &= \int_{-\infty}^\infty a_i p(t)p(t) \text{d}t \\ &= a_i \int_{-\infty}^\infty p(t)p(t) \text{d}t \\ &= a_i E_p, \end{align} where $E_p$ is the energy of the pulse $p(t)$. If $E_p=1$, then the matched filter output is equal to the transmitted symbol. Otherwise, you need to scale the symbol by a factor $1/E_p$.

$\endgroup$
1
$\begingroup$

I recommand to have the input and the output of the filter at same level (resampling included). This means a gain of 1 in linear or 0 dB.

It is consistant and useful for reuse.

The best way to preliminary verify for the gain on a low pass filter is to inject a DC constant signal.

Usualy, I tune the taps level to compensate the fractional part of the gain. And I use a left or right shifting at the output of the filter for the integer part of the gain (with a rounding).

$\endgroup$
1
$\begingroup$

If it's not obvious from the other three (at this writing) answers: scale it to match he problem at hand. All of the three suggested scalings so far (energy = 1, DC gain = 1, maximum coefficient = full scale for your data type, then scale the output) are valid in different circumstances.

And don't sweat over trying to find a universal "correct" way to do it: there isn't one. Just understand the reasons underlying the filter, and make it work for your application.

$\endgroup$
2
  • $\begingroup$ I didn't mean to imply maximum coefficient should be 1 in my answer (wasn't clear about that) but for fixed point design I believe the maximum coefficient should exceed one (one being the datapath precision) to achieve rejection that exceeds the quantization noise floor of the datapath. I updated my answer with those details. $\endgroup$ – Dan Boschen Feb 22 at 13:54
  • $\begingroup$ @DanBoschen I was thinking fixed-point fractional math -- i.e., for a 16-bit filter 0x7fff would be 1 - 1/32768, and 0x8000 would be -1. Same effect in the end, it's just how you view the numbers. I'll edit -- it does make things very unclear. $\endgroup$ – TimWescott Feb 22 at 16:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.