<jupyter_start><jupyter_text>AFSK Demodulator Step 2: Band Pass FIR Filter-----This notebook will outline the steps necessary to move the band-pass FIR filter to FPGA.This code is part of the [AFSK Demodulator on Pynq](afsk-demodulator-fpga.ipynb) project.The purpose of this code is to serve as the foundation for migrating the Python demodulator code to FPGA.  We will be streaming audio data into the FPGA and streaming processed data out from the FPGA.This is the first step of moving a demodulator processing step into the FPGA. PrerequisitesAt this point you are expected to have: * A configured PYNQ environment. * Vivado installed on your computer and configured for your board. * Experience working through the tutorials at https://pynq.readthedocs.io/. * Familiarized yourself with the AFSK demodulator implementation in Python. * Completeed the first step of the tutorial to familiarize yourself with the process of creating a streaming interface. OutlineWe are going to modify the IP we created in the first tutorial to do FIR processing.  We are going to use Python to generate the FIR coefficients (the same coefficients used in Python).We will perform the following steps in this section: 1. Create a C++ file that accepts a block of 16-bit data, performs the FIR operation, and sends the result back. 1. Create a C++ test case for the above file (because good tests improve development speed). 1. Generate an IP package from the code that can be used in Vivado. 1. Create a Zynq project in Vivado that uses the IP. 1. Export the bitstream for our project from Vivado. 1. Use Python running on the PS to load the bitstream to the PL, and verify that it works. 1. Integrate the FIR filter with the existing demodulator code, replacing the existing Python BPF.First we are going to generate the FIR filter coefficients.  Then we are going to generate some sample data for our test bench.  Filter CoefficientsWith the code below we will generate and output the coefficients for the band pass filter.  We are going to create a `cformat()` function that will allow us to copy/paste the output into a C/C++ file for HLS.<jupyter_code>%matplotlib notebook
import matplotlib.pyplot as plt

import numpy as np
from scipy.signal import lfiltic, lfilter, firwin, freqz
from scipy.io.wavfile import read

audio_file = read('../base/TNC_Test_Ver-1.102-26400-1sec.wav')
sample_rate = audio_file[0]
audio_data = audio_file[1]

bpf_coeffs = np.array(firwin(141, [1100.0/(sample_rate/2), 2300.0/(sample_rate/2)], width = None,
        pass_zero = False, scale = True, window='hann') * 32768, dtype=int)

def cformat(data):
    for i in range(0, len(data), 12):
        for j in range(12):
            if i + j == len(data): break
            print("{:6d}".format(data[i + j]), end=",")
        print()
    print()


cformat(bpf_coeffs)<jupyter_output>0,     0,     0,     0,     0,     0,     1,     3,     5,     8,     8,     5,
    -2,   -13,   -27,   -40,   -46,   -44,   -32,   -12,    11,    32,    44,    44,
    32,    14,     0,    -2,    13,    49,    97,   143,   170,   160,   104,     6,
  -118,  -244,  -340,  -381,  -352,  -258,  -120,    24,   138,   192,   173,    97,
     0,   -67,   -56,    62,   287,   575,   850,  1021,  1001,   737,   228,  -462,
 -1216, -1879, -2293, -2336, -1956, -1182,  -133,  1008,  2030,  2736,  2988,  2736,
  2030,  1008,  -133, -1182, -1956, -2336, -2293, -1879, -1216,  -462,   228,   737,
  1001,  1021,   850,   575,   287,    62,   -56,   -67,     0,    97,   173,   192,
   138,    24,  -120,  -258,  -352,  -381,  -340,  -244,  -118,     6,   104,   160,
   170,   143,    97,    49,    13,    -2,     0,    14,    32,    44,    44,    32,
    11,   -12,   -32,   -44,   -46,   -40,   -27,   -13,    -2,     5,     8,     8,
     5,     3,     1,     0,     0,     0,     0,     0,     0,<jupyter_text>NoteThe coefficients, once they have been scaled and turned into integers, have anumber of zeros at the start and end of the sequence.  Let's see what happenswhen we remove them.  Let's graph the filter response in the frequency domain.<jupyter_code>def plot_to_notebook(coeffs, sample_rate):
    plt.figure()
    plt.subplot(1, 1, 1)
    w, h = freqz(coeffs, 1, worN = 2000)
    plt.plot((sample_rate * 0.5 / np.pi) * w, 20 * np.log10(abs(h)), 'b')
    plt.title('FIR filter frequency response')
    plt.xlabel('Frequency [Hz]')
    plt.ylabel('Amplitude [dB]')
    plt.margins(0, 0.1)
    plt.grid(which='both', axis='both')
    plt.xlim(0, sample_rate/2)
    plt.axvline(1200, color='green') # cutoff frequency
    plt.axvline(2200, color='red') # cutoff frequency

plot_to_notebook(bpf_coeffs, sample_rate)<jupyter_output><empty_output><jupyter_text>Above is the filter response of the complete set of coefficients.Now lets see what happens to the response when we remove the zeros at the start and end.  We will also print out the trimmed set of coefficients.<jupyter_code>trimmed_bpf_coeffs = bpf_coeffs[6:-6]

plot_to_notebook(trimmed_bpf_coeffs, sample_rate)
cformat(trimmed_bpf_coeffs)<jupyter_output><empty_output><jupyter_text>The filter response is indistinguishable from the one with the zeros.  It will be more efficient to use fewer coefficients in our design.The above output are the filter coefficients we wil use for our bandpass filter. Test Bench DataWe will now generate the input and output data for our test bench.  Because we have a working Python model to work from, we will use its data as a baseline.<jupyter_code>class fir_filter(object):
    def __init__(self, coeffs):
        self.coeffs = coeffs
        self.zl = lfiltic(self.coeffs, 32768, [], [])
    def __call__(self, data):
        result, self.zl = lfilter(self.coeffs, 32768, data, -1, self.zl)
        return result

bpf = fir_filter(trimmed_bpf_coeffs)

data = bpf(audio_data[:264])

print("Audio Input")
cformat(audio_data[:264])
print("Filtered Audio")
cformat(np.array(data, dtype=np.int))<jupyter_output>Audio Input
   719,   748,   468,   487,   533,   880,  1187,  1717,  2124,  2262,  2417,  2371,
  2106,  1794,  1275,   690,     3,  -721, -1382, -1855, -2227, -2378, -2383, -2243,
 -1953, -1510,  -958,  -291,   214,   497,   833,   909,   818,   620,   290,  -207,
  -787, -1396, -2019, -2434, -2756, -2914, -2901, -2762, -2424, -1954, -1371,  -667,
   -66,   270,   638,   762,   762,   682,   490,   235,   100,   161,   280,   583,
   913,  1391,  1576,  1634,  1685,  1398,  1093,   658,   255,    94,     2,   105,
   349,   761,  1288,  1898,  2303,  2564,  2793,  2744,  2612,  2264,  1851,  1280,
   586,  -143,  -830, -1336, -1795, -1993, -2038, -1917, -1622, -1209,  -646,    28,
   598,   929,  1265,  1382,  1330,  1190,   843,   387,  -157,  -776, -1420, -1866,
 -2227, -2379, -2346, -2193, -1868, -1409,  -796,  -111,   557,   949,  1380,  1636,
  1604,  1550,  1310,   946,   449,  -113,  -744, -1260, -1629, -1888, -1907, -1800,
 -1579, -1171,  -623,    23,   707,  1176,  1579,  18[...]
{'fraction_non_alphanumeric': 0.11435138629507255, 'fraction_numerical': 0.13173394627208734, 'mean_word_length': 2.8379272326350606, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '390388', 'license': 'MIT', 'n_tokens_mistral': 3053, 'n_tokens_neox': 2509, 'n_words': 909, 'path': 'band_pass_filter/afsk-demodulator-fpga.ipynb', 'repo_name': 'mobilinkd/afsk-demodulator-pynq', 'size': '6961'}