Languages

CommunityCategory: XMODELDesigning filter coefficients using SciPy

XMODEL

Designing filter coefficients using SciPy

SA Support Team Staff 2019-05-24

We can design various types of filters such as Butterworth, Bessel, Chebyshev I/II, and elliptic filters using SciPy’s signal processing library. Can you show an example of converting the filter coefficients designed this way to the parameters of the XMODEL’s 'filter' primitive?

2 Answers
Best Answer
SA Support Team Staff 2019-05-24

Here is an example of designing a Chebyshev-II type low-pass filter from the passband/stopband specifications using the cheb2ord() and cheby2() functions from the scipy.signal library. Please refer to the Scipy documentations (https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.signal.cheb2ord.html) for more detailed descriptions.

#!/usr/bin/env xmodelpy

import scipy.signal
import numpy as np

# design of low-pass chebyshev-II filter
wp = 2*np.pi * 0.9e6    # passband edge frequency (rad/s)
ws = 2*np.pi * 1.1e6    # stopband edge frequency (rad/s)
rp = 3                  # maximum attenuation within pass band in dB
rs = 40                 # minimum attenuation within stop band in dB

N, wn = scipy.signal.cheb2ord(wp, ws, rp, rs, analog=True)
z,p,k = scipy.signal.cheby2(N, rs, wn, 'lowpass', analog=True, output='zpk')

And here is an example of designing a Chebyshev-II type band-pass filter:

# design of bass-pass chebyshev-II filter
wp = [2*np.pi*1.1e6, 2*np.pi*2.9e6]     # passband edge frequency (rad/s)
ws = [2*np.pi*0.9e6, 2*np.pi*3.1e6]     # stopband edge frequency (rad/s)
rp = 3                                  # maximum attenuation within pass band in dB
rs = 40                                 # minimum attenuation within stop band in dB

N, wn = scipy.signal.cheb2ord(wp, ws, rp, rs, analog=True)
z,p,k = scipy.signal.cheby2(N, rs, wn, 'bandpass', analog=True, output='zpk')

Similarly, you can also design Chebyshev-I type filters using cheb1ord() and cheby1(), Butterworth-type filters using buttord() and butter(), and elliptic-type filters using ellipord() and ellip() functions. Wikipedia describes the differences between these filter types in details (https://en.wikipedia.org/wiki/Chebyshev_filter).

In the above examples, the z, p, k results store the zero frequencies, pole frequencies, and gain value of the designed linear filter, respectively. There are a few considerations when you use them as the 'poles', 'zeros' and 'gain' parameters for the XMODEL's 'filter' primitive:

1. The 'poles' and 'zeros' parameters list the pole/zero frequency values in Hertz whereas SciPy returns pole/zeros frequencies in rad/s.
2. The real part of the pole/zero frequencies listed in the 'poles' and 'zeros' parameters is positive for left-half plane (LHP) poles/zeros and negative for right-half plane (RHP) poles/zeros. For SciPy results, it is negative for LHP and positive for RHP poles/zeros.
3. The 'poles' and 'zeros' parameters list the real and imaginary parts of each pole/zero frequency as separate numbers as below. It is because SystemVerilog does not natively support complex numbers:

poles = '{real(fp1), imag(fp1), real(fp2), imag(fp2), ..., real(fpM), imag(fpM)}
zeros = '{real(fz1), imag(fz1), real(fz2), imag(fz2), ..., real(fzN), imag(fzN)}

4. The complex poles/zeros listed in the 'poles' and 'zeros' parameters must be in exact conjugate pairs.
5. To express a filter transfer function without any zeros, the 'zeros' parameter should be defined as '{0.0}.
6. For a strictly proper system, the number of poles of a 'filter' primitive must be greater than the number of zeros. To satisfy this requirement, you may need to add a dummy pole (e.g. a pole at 1THz) that does not affect the filter characteristics.

For example, for z, p, k values shown below:

z = [ -3.72529030e-09+16418754.44763247j  -5.23868948e-09-16418754.44763249j
        1.92618690e-09 +6800870.76948295j   7.78721962e-11 -6800870.76948295j]
p = [ -1075430.76302337+2991438.64240786j -3170099.69183802+1512931.2492777j
      -3170099.69183801-1512931.2492777j  -1075430.76302337-2991438.64240787j]
k = (0.01+0j)

The corresponding filter instance may be:

filter #(.gain(0.01),
    .poles('{171160, 476102, 504537, 240790, 504537, -240790, 171160, -476102, 1e12, 0.0}),
    .zeros('{0.0, 2613125.9, 0.0, -2613125.9, 0.0, 1082392.2, 0.0, -1082392.2})) 
    filter_inst (.in(in), .out(out));

Note that the pole/zero frequencies are reordered to arrange the complex poles/zeros in conjugate pairs. Also, a dummy pole at a frequency well above the other pole/zero frequencies (1THz) is added in order to keep the filter strictly proper.

Here is one way to convert z, p, k values to gain, poles, zeros parameters:

    def sort_pz(p):
        res = []
        for w in sorted(p, key=np.real):
            f = w / (2*np.pi)
            if f.imag == 0.0:
                res += [f.real, 0.0]
            elif f.imag > 0.0:
                res += [f.real, f.imag, f.real, -f.imag]
        return res

    gain = (k / np.prod(-p) * np.prod(-z)).real
    poles = sort_pz(-p)
    zeros = sort_pz(-z)
    if len(p) <= len(z): poles += [1e12, 0.0] * (len(z)-len(p)+1)
    if len(z) == 0: zeros += [0.0]

    print("gain  : %g" % gain)
    print("poles : %s" % ', '.join(['%g' % v for v in poles]))
    print("zeros : %s" % ', '.join(['%g' % v for v in zeros]))
SA Support Team Staff 2022-09-30

The attachment is a Python script named 'filter_design.py', which can design a filter that satisfies your specifications and display its transfer function using the parameters of the XMODEL 'filter' primitive. With this script, one can model various common-class filters in XMODEL in a more convenient fashion.
The filter specifications are defined using the script's command-line options. The list of available options and their usages are displayed when you run the script on the command-line with the '-h' option.

xmodelpy filter_design.py -h

Result:

Usage: filter_design.py [options]
Design a common-class filter.

Options:
  -h, --help            show this help message and exit
  -f FREQ_or_FREQ1,FREQ2, --freq=FREQ_or_FREQ1,FREQ2
                        Critical frequency or frequencies in Hz (default:
                        1.0). For lowpass and highpass filters, it is a single
                        frequency value defining the cut-off; for bandpass and
                        bandstop filters, it is a pair of frequency values
                        separated by a comma (,) defining the low-side and
                        high-side cut-offs.
  -n ORDER, --order=ORDER
                        Order of the filter (default: 2).
  -t FILTER_TYPE, --type=FILTER_TYPE
                        Filter type (choices: lowpass, highpass, bandpass,
                        bandstop); the default is 'lowpass'.
  -c FILTER_CLASS, --class=FILTER_CLASS
                        Filter class (choices: butter, cheby1, cheby2, ellip,
                        bessel); the default is 'butter'.
  --rpass=MAX_ATTEN_PASS, --ripple-pass=MAX_ATTEN_PASS
                        Maximum attenuation allowed in passband in dB
                        (default: 3.0); used for cheby1 and ellip filters.
  --rstop=MIN_ATTEN_STOP, --ripple-stop=MIN_ATTEN_STOP
                        Minimum attenuation required in stopband in dB
                        (default: 30.0); used for cheby2 and ellip filters.
  --plot, --show-plot   Show the bode plot of the designed filter.

The following example illustrates a case of modeling a Bessel-type, fourth-order, low-pass filter with a 1GHz cut-off frequency. With the '--plot' option, the script displays the transfer function of the filter in Bode plots.

xmodelpy filter_design.py --freq 1e9 --type lowpass --order 4 --class bessel --plot

Result:

*** FILTER PRIMITIVE PARAMETERS:
gain  : 1
poles : 6.57211e+08, 8.30161e+08, 6.57211e+08, -8.30161e+08, 9.04759e+08, 2.70919e+08, 9.04759e+08, -2.70919e+08
zeros : 0

The next example illustrates a case of modeling an elliptic-type, sixth-order, band-pass filter with 0.1GHz and 1GHz cut-off frequencies. An elliptic filter may have ripples in its passband and stopband responses, and the '--rpass' option specifies the maximum attenuation allowed in the passband in dBs while the '--rstop' option specifies the minimum attenuation required in the stopband in dBs.

xmodelpy filter_design.py --freq 0.1e9,1e9 --type bandpass --order 6 --class ellip \
--rpass 3 --rstop 40 --plot

Result:

*** FILTER PRIMITIVE PARAMETERS:
gain  : 0.0100029
poles : 964446, 1.00503e+08, 964446, -1.00503e+08, 6.5194e+06, 1.10333e+08, 6.5194e+06, -1.10333e+08, 9.54733e+06, 9.94906e+08, 9.54733e+06, -9.94906e+08, 4.35826e+07, 1.71065e+08, 4.35826e+07, -1.71065e+08, 5.33682e+07, 9.03193e+08, 5.33682e+07, -9.03193e+08, 1.39856e+08, 5.48943e+08, 1.39856e+08, -5.48943e+08, 1e+12, 0
zeros : -4.37263e-08, 2.41733e+09, -4.37263e-08, -2.41733e+09, -4.08238e-08, 9.45706e+07, -4.08238e-08, -9.45706e+07, -2.99276e-08, 1.05741e+09, -2.99276e-08, -1.05741e+09, 5.3513e-09, 4.1368e+07, 5.3513e-09, -4.1368e+07, 1.45881e-07, 8.45223e+07, 1.45881e-07, -8.45223e+07, 4.9254e-07, 1.18312e+09, 4.9254e-07, -1.18312e+09

Attachment: filter_design.py