---
# System prepended metadata

title: 軟硬體協同設計Final Project

---

# 軟硬體協同設計Final Project
### ●　作業標題：FPGA audio jack 實作
### ●　班級姓名座號：國立高雄大學電機系大四 B1095117 黃致衡
### ●　授課老師：林宏益教授

---
### 展示影片：https://youtu.be/FWSER7mHw-U?si=-lFa7CzOHpmeE7Ff
---
## 一、作業內容：
###
使用 PYNQ-Z2 開發板，結合Pmod I2S2 進行音訊撥放以及音頻處理。

## 二、實作步驟：

### 1. 將設計濾波器所需使用到的參數進行生成 (filter_gen.m)
```
%--------------------------------------------------------------------------
% Copyright (c) 2023 Syed Fahad
%
% Project Name: Real-time Audio Processing through FIR filters on Basys-3
% Description: Script to generate hexadecimal coefficients for a desired FIR filter
%
%--------------------------------------------------------------------------

sampling_freq = 44100;
cutoff_freq = 1000;
taps = 89; % Number of taps of the filter
coeff_width = 16; % Width of coefficients in bits
A = int32(fir1(taps - 1, cutoff_freq / (sampling_freq / 2), 'low') * (2^(coeff_width - 1) - 1));

freqz(double(A) / (2^(coeff_width - 1) - 1)); % display for sanity check

for i = 1:length(A)
    hex_value = dec2hex(abs(A(i)),coeff_width / 4); % convert to hex
    if A(i) < 0 % if negative, display sign
        printf('-');
    end
    printf('%d"h%s, ', coeff_width, hex_value); % print result

    if mod(i, 5) == 0
        printf('\n');
    end
end
```
### 2. 撰寫 FIR 濾波器的 HDL 檔案 (fir.sv)
```
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023 Syed Fahad
//
// Create Date: 01/28/2023 01:47:52 PM
// Project Name: Real-time Audio Processing through FIR filters on Basys-3
// Description: Implementation of a single/dual channel FIR filter module which
//  handles and connects the AXI stream to FIR filter backend
//
//////////////////////////////////////////////////////////////////////////////////

module dual_channel_fir_filter #(
    parameter DATA_WIDTH = 24,
    parameter N_FILTERS = 4 // Number of filters implemented
) (
    input wire clk,
    input wire [N_FILTERS-1:0] sw,
    
    //AXIS SLAVE INTERFACE
    input  wire [DATA_WIDTH-1:0] s_axis_data,
    input  wire s_axis_valid,
    output reg  s_axis_ready = 1'b1,
    input  wire s_axis_last,
    
    // AXIS MASTER INTERFACE
    output reg [DATA_WIDTH-1:0] m_axis_data = 1'b0,
    output reg m_axis_valid = 1'b0,
    input  wire m_axis_ready,
    output reg m_axis_last = 1'b0
);
    reg signed [DATA_WIDTH-1:0] input_data [1:0];    // Left and right channel data
    wire signed [DATA_WIDTH-1:0] output_data [1:0];    // Left and right channel data
    
    wire m_select = m_axis_last;
    wire m_new_word = (m_axis_valid == 1'b1 && m_axis_ready == 1'b1) ? 1'b1 : 1'b0;
    wire m_new_packet = (m_new_word == 1'b1 && m_axis_last == 1'b1) ? 1'b1 : 1'b0;
    
    wire s_select = s_axis_last;
    wire s_new_word = (s_axis_valid == 1'b1 && s_axis_ready == 1'b1) ? 1'b1 : 1'b0;
    wire s_new_packet = (s_new_word == 1'b1 && s_axis_last == 1'b1) ? 1'b1 : 1'b0;
    reg s_new_packet_r = 1'b0;
    
    dual_channel_fir_engine fir_engine(
        .clk(clk),
        .sw(sw),
        .new_packet(s_new_packet_r),
        .input_data(input_data),
        .output_data(output_data)
    );
    always@(posedge clk) begin
        s_new_packet_r <= s_new_packet;
        
        if (s_new_word == 1'b1) // Register AXIS slave data
            input_data[s_select] <= s_axis_data;
    end
            
    
    // Controls the AXIS master interface by setting the validity and end-of-packet signals based on the state of the AXIS slave interface.
    always@(posedge clk)
        if (s_new_packet_r == 1'b1)
            m_axis_valid <= 1'b1;
        else if (m_new_packet == 1'b1)
            m_axis_valid <= 1'b0;
            
    always@(posedge clk)
        if (m_new_packet == 1'b1)
            m_axis_last <= 1'b0;
        else if (m_new_word == 1'b1)
            m_axis_last <= 1'b1;
    
    // Assigns the output data on the AXIS master interface based on the validity and selection signals.
    always@(m_axis_valid, output_data[0], output_data[1], m_select)
        if (m_axis_valid == 1'b1)
            m_axis_data = output_data[m_select];
        else
            m_axis_data = 'b0;
            
    always@(posedge clk)
        if (s_new_packet == 1'b1)
            s_axis_ready <= 1'b0;
        else if (m_new_packet == 1'b1)
            s_axis_ready <= 1'b1;
endmodule

module single_channel_fir_filter #(
    parameter DATA_WIDTH = 24,
    parameter N_FILTERS = 4 // Number of filters implemented
) (
    input wire clk,
    input wire [N_FILTERS-1:0] sw,
    
    //AXIS SLAVE INTERFACE
    input  wire [DATA_WIDTH-1:0] s_axis_data,
    input  wire s_axis_valid,
    output reg  s_axis_ready = 1'b1,
    input  wire s_axis_last,
    
    // AXIS MASTER INTERFACE
    output reg [DATA_WIDTH-1:0] m_axis_data = 1'b0,
    output reg m_axis_valid = 1'b0,
    input  wire m_axis_ready,
    output reg m_axis_last = 1'b0
);
    reg signed [DATA_WIDTH-1:0] input_data [1:0];    // Left and right channel data
    wire signed [DATA_WIDTH-1:0] output_data;    // Combined output data
    
    wire m_select = m_axis_last;
    wire m_new_word = (m_axis_valid == 1'b1 && m_axis_ready == 1'b1) ? 1'b1 : 1'b0;
    wire m_new_packet = (m_new_word == 1'b1 && m_axis_last == 1'b1) ? 1'b1 : 1'b0;
    
    wire s_select = s_axis_last;
    wire s_new_word = (s_axis_valid == 1'b1 && s_axis_ready == 1'b1) ? 1'b1 : 1'b0;
    wire s_new_packet = (s_new_word == 1'b1 && s_axis_last == 1'b1) ? 1'b1 : 1'b0;
    reg s_new_packet_r = 1'b0;
    
    single_channel_fir_engine fir_engine(
        .clk(clk),
        .sw(sw),
        .new_packet(s_new_packet_r),
        .input_data(input_data),
        .output_data(output_data)
    );
    always@(posedge clk) begin
        s_new_packet_r <= s_new_packet;
        
        if (s_new_word == 1'b1) // Register AXIS slave data
            input_data[s_select] <= s_axis_data;
    end
    
    // Controls the AXIS master interface by setting the validity and end-of-packet signals based on the state of the AXIS slave interface.
    always@(posedge clk)
        if (s_new_packet_r == 1'b1)
            m_axis_valid <= 1'b1;
        else if (m_new_packet == 1'b1)
            m_axis_valid <= 1'b0;
            
    always@(posedge clk)
        if (m_new_packet == 1'b1)
            m_axis_last <= 1'b0;
        else if (m_new_word == 1'b1)
            m_axis_last <= 1'b1;
    
    // Assigns the output data on the AXIS master interface based on the validity and selection signals.
    always@(m_axis_valid, output_data, m_select)
        if (m_axis_valid == 1'b1)
            m_axis_data = output_data;
        else
            m_axis_data = 'b0;
            
    always@(posedge clk)
        if (s_new_packet == 1'b1)
            s_axis_ready <= 1'b0;
        else if (m_new_packet == 1'b1)
            s_axis_ready <= 1'b1;
endmodule
```
### 3. 撰寫 FIR 濾波器的後端結構 HDL 檔案 (fir_backend.sv)
```
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023 Syed Fahad
// 
// Create Date: 01/28/2023 01:47:52 PM
// Project Name: Real-time Audio Processing through FIR filters on Basys-3
// Description: Implementation of a single/dual channel FIR filter backend
// 
//////////////////////////////////////////////////////////////////////////////////

module dual_channel_fir_engine #(
    parameter DATA_WIDTH = 24,
    parameter N_FILTERS = 4, // Number of filters implemented
    parameter N_TAPS = 45,
    parameter COEFF_WIDTH = 16
) (
    input wire clk,
    input wire [N_FILTERS-1:0] sw,
    input wire new_packet,
    
    input reg signed [DATA_WIDTH-1:0] input_data [1:0],
    output reg signed [DATA_WIDTH-1:0] output_data [1:0],
    
    output reg signed [DATA_WIDTH-1:0] buffer [N_TAPS-1:0][1:0],  // Buffer for data
    output reg signed [COEFF_WIDTH+DATA_WIDTH-1:0] op_buffer [1:0],
    
    output reg [2:0] selected_filter
);
    // Coefficients for FIR filter
   reg signed [COEFF_WIDTH-1:0] coeffs [N_FILTERS-1:0][N_TAPS-1:0] = '{
   '{ // Low-pass filter: 1KHz
        16'h0007, 16'h0015, 16'h002B, 16'h004C, 16'h007E,
        16'h00C5, 16'h0127, 16'h01A6, 16'h0243, 16'h02FF,
        16'h03D9, 16'h04CC, 16'h05D4, 16'h06EA, 16'h0807,
        16'h0920, 16'h0A2E, 16'h0B25, 16'h0BFF, 16'h0CB1,
        16'h0D35, 16'h0D87, 16'h0DA3, 16'h0D87, 16'h0D35,
        16'h0CB1, 16'h0BFF, 16'h0B25, 16'h0A2E, 16'h0920,
        16'h0807, 16'h06EA, 16'h05D4, 16'h04CC, 16'h03D9,
        16'h02FF, 16'h0243, 16'h01A6, 16'h0127, 16'h00C5,
        16'h007E, 16'h004C, 16'h002B, 16'h0015, 16'h0007
    },
    '{ // High pass filter: 2KHz
        16'h0003, 16'h000F, 16'h001F, 16'h0035, 16'h0053,
        16'h0076, 16'h0098, 16'h00B3, 16'h00BB, 16'h00A4,
        16'h0062, 16'hFFEB, 16'hFF38, 16'hFE47, 16'hFD1E,
        16'hFBC7, 16'hFA54, 16'hF8DC, 16'hF778, 16'hF642,
        16'hF551, 16'hF4B8, 16'h7485, 16'hF4B8, 16'hF551,
        16'hF642, 16'hF778, 16'hF8DC, 16'hFA54, 16'hFBC7,
        16'hFD1E, 16'hFE47, 16'hFF38, 16'hFFEB, 16'h0062,
        16'h00A4, 16'h00BB, 16'h00B3, 16'h0098, 16'h0076,
        16'h0053, 16'h0035, 16'h001F, 16'h000F, 16'h0003
    },
    '{ // Bandpass: 1KHz to 4KHz
        16'hFFF3, 16'hFFB9, 16'hFF79, 16'hFF3B, 16'hFF16,
        16'hFF26, 16'hFF74, 16'hFFE3, 16'h0024, 16'hFFC6,
        16'hFE5D, 16'hFBBB, 16'hF826, 16'hF468, 16'hF1B5,
        16'hF15D, 16'hF45E, 16'hFAFF, 16'h0495, 16'h0F8A,
        16'h19B7, 16'h20EA, 16'h2384, 16'h20EA, 16'h19B7,
        16'h0F8A, 16'h0495, 16'hFAFF, 16'hF45E, 16'hF15D,
        16'hF1B5, 16'hF468, 16'hF826, 16'hFBBB, 16'hFE5D,
        16'hFFC6, 16'h0024, 16'hFFE3, 16'hFF74, 16'hFF26,
        16'hFF16, 16'hFF3B, 16'hFF79, 16'hFFB9, 16'hFFF3
    },
    '{ // Moving Average Filter
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0,
        16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0, 16'h05B0
    }};
    
    always@(posedge clk) begin
        // Update the currently selected filter
        case (sw)
            4'b0000: selected_filter <= 0;
            4'b0001: selected_filter <= 4;
            4'b0010: selected_filter <= 3;
            4'b0100: selected_filter <= 2;
            4'b1000: selected_filter <= 1;
            default: selected_filter <= 0;
        endcase
            
        // Hold the latest input on top of buffer (buffer[N_TAPS - 1])
        if (new_packet == 1'b1) begin // New packet recieved
            buffer[N_TAPS - 1][0] <= input_data[0];
            buffer[N_TAPS - 1][1] <= input_data[1];
        end
    end

    // The input buffer acts like a large shift register
    generate
    for (genvar i = 0; i < N_TAPS - 1; i = i+1) begin
        always@(posedge clk)
            if (new_packet == 1'b1) begin // New packet recieved
                // Shift the old packets so that buffer[N_TAPS - 1] holds the latest one
                buffer[i][0]  <= buffer[i + 1][0];
                buffer[i][1]  <= buffer[i + 1][1];
            end
    end
    endgenerate
    
    // Actually compute the output
    always@(posedge clk)
        if (new_packet == 1'b1 && selected_filter != 0) begin // New packet recieved
            op_buffer[0] = coeffs[selected_filter - 1][0] * buffer[N_TAPS - 1][0];
            op_buffer[1] = coeffs[selected_filter - 1][0] * buffer[N_TAPS - 1][1];
            for (int i = 1; i < N_TAPS; i = i+1) begin
                op_buffer[0] = op_buffer[0] + coeffs[selected_filter - 1][i] * buffer[N_TAPS - i - 1][0];
                op_buffer[1] = op_buffer[1] + coeffs[selected_filter - 1][i] * buffer[N_TAPS - i - 1][1];
            end
            if (op_buffer[0][COEFF_WIDTH+DATA_WIDTH-1] == 1'b1) begin   // If is negative
                output_data[0] = -((-op_buffer[0]) >> COEFF_WIDTH);     // Convert to +ve for shifting
            end
            else output_data[0] <= op_buffer[0][COEFF_WIDTH+DATA_WIDTH-1:COEFF_WIDTH];
            if (op_buffer[1][COEFF_WIDTH+DATA_WIDTH-1] == 1'b1) begin   // If is negative
                output_data[1] = -((-op_buffer[1]) >> COEFF_WIDTH);     // Convert to +ve for shifting
            end
            else output_data[1] <= op_buffer[1][COEFF_WIDTH+DATA_WIDTH-1:COEFF_WIDTH];
        end
        else if (new_packet == 1'b1 && selected_filter == 0) begin
                output_data[0] <= input_data[0];
                output_data[1] <= input_data[1];
        end
endmodule

module single_channel_fir_engine #(
    parameter DATA_WIDTH = 24,
    parameter N_FILTERS = 4, // Number of filters implemented
    parameter N_TAPS = 89,
    parameter COEFF_WIDTH = 16
) (
    input wire clk,
    input wire [N_FILTERS-1:0] sw,
    input wire new_packet,
    
    input reg signed [DATA_WIDTH-1:0] input_data [1:0],
    output reg signed [DATA_WIDTH-1:0] output_data,
    
    output reg signed [DATA_WIDTH-1:0] buffer [N_TAPS-1:0],  // Buffer for data
    output reg signed [COEFF_WIDTH+DATA_WIDTH-1:0] op_buffer,
    
    output reg [2:0] selected_filter
);
    // --
   reg signed [COEFF_WIDTH-1:0] coeffs [N_FILTERS-1:0][N_TAPS-1:0] = '{
   '{ // Low-pass filter: 1KHz
        -16'h0006, -16'h0011, -16'h001C, -16'h0027, -16'h0031,
        -16'h003A, -16'h0040, -16'h0041, -16'h003B, -16'h002C,
        -16'h0013, 16'h0011, 16'h003D, 16'h0071, 16'h00A7,
        16'h00DA, 16'h0104, 16'h011C, 16'h011D, 16'h00FF,
        16'h00BF, 16'h005C, -16'h002A, -16'h00CB, -16'h017C,
        -16'h0231, -16'h02D7, -16'h035B, -16'h03A7, -16'h03AA,
        -16'h0351, -16'h028F, -16'h015E, 16'h0043, 16'h024D,
        16'h04B0, 16'h0755, 16'h0A22, 16'h0CF4, 16'h0FA8,
        16'h121B, 16'h142A, 16'h15BA, 16'h16B3, 16'h1707,
        16'h16B3, 16'h15BA, 16'h142A, 16'h121B, 16'h0FA8,
        16'h0CF4, 16'h0A22, 16'h0755, 16'h04B0, 16'h024D,
        16'h0043, -16'h015E, -16'h028F, -16'h0351, -16'h03AA,
        -16'h03A7, -16'h035B, -16'h02D7, -16'h0231, -16'h017C,
        -16'h00CB, -16'h002A, 16'h005C, 16'h00BF, 16'h00FF,
        16'h011D, 16'h011C, 16'h0104, 16'h00DA, 16'h00A7,
        16'h0071, 16'h003D, 16'h0011, -16'h0013, -16'h002C,
        -16'h003B, -16'h0041, -16'h0040, -16'h003A, -16'h0031,
        -16'h0027, -16'h001C, -16'h0011, -16'h0006
    },
    '{ // High pass filter: 2KHz
        16'h0003, 16'h0008, 16'h000E, 16'h0013, 16'h0019,
        16'h001D, 16'h0020, 16'h0020, 16'h001D, 16'h0016,
        16'h0009, -16'h0008, -16'h001F, -16'h0038, -16'h0053,
        -16'h006D, -16'h0082, -16'h008E, -16'h008E, -16'h007F,
        -16'h005F, -16'h002E, 16'h0015, 16'h0065, 16'h00BE,
        16'h0118, 16'h016B, 16'h01AC, 16'h01D2, 16'h01D4,
        16'h01A7, 16'h0147, 16'h00AE, -16'h0022, -16'h0126,
        -16'h0256, -16'h03A8, -16'h050E, -16'h0676, -16'h07CF,
        -16'h0907, -16'h0A0F, -16'h0AD6, -16'h0B52, 16'h7483,
        -16'h0B52, -16'h0AD6, -16'h0A0F, -16'h0907, -16'h07CF,
        -16'h0676, -16'h050E, -16'h03A8, -16'h0256, -16'h0126,
        -16'h0022, 16'h00AE, 16'h0147, 16'h01A7, 16'h01D4,
        16'h01D2, 16'h01AC, 16'h016B, 16'h0118, 16'h00BE,
        16'h0065, 16'h0015, -16'h002E, -16'h005F, -16'h007F,
        -16'h008E, -16'h008E, -16'h0082, -16'h006D, -16'h0053,
        -16'h0038, -16'h001F, -16'h0008, 16'h0009, 16'h0016,
        16'h001D, 16'h0020, 16'h0020, 16'h001D, 16'h0019,
        16'h0013, 16'h000E, 16'h0008, 16'h0003
    },
    '{ // Bandpass: 1KHz to 4KHz
        -16'h0002, -16'h0010, -16'h0017, -16'h0013, 16'h0001,
        16'h0025, 16'h0054, 16'h0086, 16'h00AD, 16'h00BA,
        16'h00A6, 16'h0073, 16'h0031, -16'h0006, -16'h0014,
        16'h001B, 16'h0089, 16'h011D, 16'h01A8, 16'h01EC,
        16'h01B6, 16'h00EE, -16'h0057, -16'h01D6, -16'h032C,
        -16'h03F4, -16'h03EA, -16'h030B, -16'h01A5, -16'h004A,
        16'h0050, -16'h0071, -16'h02D8, -16'h06B4, -16'h0B46,
        -16'h0F67, -16'h11BF, -16'h1127, -16'h0CFB, -16'h055D,
        16'h04C0, 16'h0FB6, 16'h198B, 16'h2059, 16'h22C8,
        16'h2059, 16'h198B, 16'h0FB6, 16'h04C0, -16'h055D,
        -16'h0CFB, -16'h1127, -16'h11BF, -16'h0F67, -16'h0B46,
        -16'h06B4, -16'h02D8, -16'h0071, 16'h0050, -16'h004A,
        -16'h01A5, -16'h030B, -16'h03EA, -16'h03F4, -16'h032C,
        -16'h01D6, -16'h0057, 16'h00EE, 16'h01B6, 16'h01EC,
        16'h01A8, 16'h011D, 16'h0089, 16'h001B, -16'h0014,
        -16'h0006, 16'h0031, 16'h0073, 16'h00A6, 16'h00BA,
        16'h00AD, 16'h0086, 16'h0054, 16'h0025, 16'h0001,
        -16'h0013, -16'h0017, -16'h0010, -16'h0002
    },
    '{ // Moving Average Filter
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0,
        16'h02E0, 16'h02E0, 16'h02E0, 16'h02E0
    }};
    
    always@(posedge clk) begin
        // Update the currently selected filter
        case (sw)
            4'b0000: selected_filter <= 0;
            4'b0001: selected_filter <= 4;
            4'b0010: selected_filter <= 3;
            4'b0100: selected_filter <= 2;
            4'b1000: selected_filter <= 1;
            default: selected_filter <= 0;
        endcase
            
        // Hold the latest input on top of buffer (buffer[N_TAPS - 1])
        if (new_packet == 1'b1) begin // New packet recieved
            buffer[N_TAPS - 1] <= (input_data[0] + input_data[1]) / 2;
        end
    end
            
    generate
    for (genvar i = 0; i < N_TAPS - 1; i = i+1) begin
        always@(posedge clk)
            if (new_packet == 1'b1) begin // New packet recieved
                // Shift the old packets so that buffer[N_TAPS - 1] holds the latest one
                buffer[i]  <= buffer[i + 1];
            end
    end
    endgenerate
    
    // Actually compute the output
    always@(posedge clk)
        if (new_packet == 1'b1 && selected_filter != 0) begin // New packet recieved
            op_buffer = coeffs[selected_filter - 1][0] * buffer[N_TAPS - 1];
            for (int i = 1; i < N_TAPS; i = i+1)
                op_buffer = op_buffer + coeffs[selected_filter - 1][i] * buffer[N_TAPS - i - 1];
            
            
            if (op_buffer[COEFF_WIDTH+DATA_WIDTH-1] == 1'b1)   // If is negative
                output_data = -((-op_buffer) >> COEFF_WIDTH);     // Convert to +ve for shifting
            else output_data <= op_buffer[COEFF_WIDTH+DATA_WIDTH-1:COEFF_WIDTH];
            
        end
        else if (new_packet == 1'b1 && selected_filter == 0)
            output_data <= (input_data[0] + input_data[1]) / 2;
endmodule

```
### 4. 撰寫單通道的 FIR 後端結構 HDL 檔案 (single_channel_fir_engine_tb.sv)
```
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023 Syed Fahad
//
// Create Date: 01/29/2023 05:13:37 PM
// Project Name: Real-time Audio Processing through FIR filters on Basys-3
// Description: Testbench for single-channel FIR filter backend
//
//////////////////////////////////////////////////////////////////////////////////


module single_channel_fir_filter_tb;
  reg clk;
  reg [3:0] sw;
  reg new_packet;
  reg signed [23:0] input_data [1:0];
  wire signed [23:0] output_data;
  wire signed [24-1:0] buffer [89-1:0];  // Buffer for data
  wire signed [16+24-1:0] op_buffer;

  wire [2:0] selected_filter;

  // Instantiate the fir_filter module
  single_channel_fir_engine uut (
    .clk(clk),
    .sw(sw),
    .new_packet(new_packet),
    .input_data(input_data),
    .output_data(output_data),
    .buffer(buffer),
    .op_buffer(op_buffer),
    .selected_filter(selected_filter)
  );

  // Clock generation for the testbench
  initial begin
    input_data[0] <= 24'h000000;
    input_data[1] <= 24'h000000;
    new_packet <= 1;
    sw <= 4'b0001;
    clk = 0;
    forever #1 clk = ~clk;
  end

  // Initialize inputs for the test
  initial begin
    // Set up initial values for inputs
    #178.5
    input_data[0] <= 24'hF00000;
    input_data[1] <= 24'h00000F;
    #1
    input_data[0] <= 24'h000000;
    input_data[1] <= 24'h000000;
    
    // TODO Add more data...
  end
endmodule
```
### 5. 撰寫 Pmod I2S2 所需的 HDL 檔案 (axis_i2s2.v)
```
`timescale 1ns / 1ps
`default_nettype none
//////////////////////////////////////////////////////////////////////////////////
// Company: Digilent
// Engineer: Arthur Brown
// 
// Create Date: 03/23/2018 01:23:15 PM
// Module Name: axis_i2s2
// Description: AXI-Stream I2S controller intended for use with Pmod I2S2.
//              Generates clocks and select signals required to place each of the ICs on the Pmod I2S2 into slave mode.
//              Data is 24-bit, left aligned, shifted one serial clock right from the LRCK boundaries.
//              This module only supports 44.1KHz sample rate, and expects the frequency of axis_clk to be approx 22.591MHz.
//              At the end of each I2S frame, a 2-word packet is made available on the AXIS master interface. Further packets will be discarded
//              until the current packet is accepted by an AXIS slave.
//              Whenever a 2-word packet is received on the AXIS slave interface, it is transmitted over the I2S interface on the next frame.
//              Each packet consists of two 3-byte words, starting with left audio channel data, followed by right channel data.
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

module axis_i2s2 (
    input  wire        axis_clk, // require: approx 22.591MHz
    input  wire        axis_resetn,
    
    input  wire [31:0] tx_axis_s_data,
    input  wire        tx_axis_s_valid,
    output reg         tx_axis_s_ready = 1'b0,
    input  wire        tx_axis_s_last,
    
    output wire [31:0] rx_axis_m_data,
    output reg         rx_axis_m_valid = 1'b0,
    input  wire        rx_axis_m_ready,
    output reg         rx_axis_m_last = 1'b0,
    
    output wire tx_mclk, // JA[0]
    output wire tx_lrck, // JA[1]
    output wire tx_sclk, // JA[2]
    output reg  tx_sdout, // JA[3]
    output wire rx_mclk, // JA[4]
    output wire rx_lrck, // JA[5]
    output wire rx_sclk, // JA[6]
    input  wire rx_sdin // JA[7]
);
    reg [8:0] count = 9'd0;
    localparam EOF_COUNT = 9'd455; // end of full I2S frame
    
    always@(posedge axis_clk)
        count <= count + 1;
    
    wire lrck = count[8];
    wire sclk = count[2];
    wire mclk = axis_clk;
    assign tx_lrck = lrck;
    assign tx_sclk = sclk;
    assign tx_mclk = mclk;
    assign rx_lrck = lrck;
    assign rx_sclk = sclk;
    assign rx_mclk = mclk;
    
    /* AXIS SLAVE CONTROLLER */
    reg [31:0] tx_data_l = 0;
    reg [31:0] tx_data_r = 0;
    
    always@(posedge axis_clk)
        if (axis_resetn == 1'b0)
            tx_axis_s_ready <= 1'b0;
        else if (tx_axis_s_ready == 1'b1 && tx_axis_s_valid == 1'b1 && tx_axis_s_last == 1'b1) // end of packet, cannot accept data until current one has been transmitted
            tx_axis_s_ready <= 1'b0;
        else if (count == 9'b0) // beginning of I2S frame, in order to avoid tearing, cannot accept data until frame complete
            tx_axis_s_ready <= 1'b0;
        else if (count == EOF_COUNT) // end of I2S frame, can accept data
            tx_axis_s_ready <= 1'b1;
            
    always@(posedge axis_clk)
        if (axis_resetn == 1'b0) begin
            tx_data_r <= 32'b0;
            tx_data_l <= 32'b0;
        end else if (tx_axis_s_valid == 1'b1 && tx_axis_s_ready == 1'b1)
            if (tx_axis_s_last == 1'b1)
                tx_data_r <= tx_axis_s_data;
            else
                tx_data_l <= tx_axis_s_data;
    
    /* I2S TRANSMIT SHIFT REGISTERS */
    reg [23:0] tx_data_l_shift = 24'b0;
    reg [23:0] tx_data_r_shift = 24'b0;
    
    always@(posedge axis_clk)
        if (count == 3'b000000111) begin
            tx_data_l_shift <= tx_data_l[23:0];
            tx_data_r_shift <= tx_data_r[23:0];
        end else if (count[2:0] == 3'b111 && count[7:3] >= 5'd1 && count[7:3] <= 5'd24) begin
            if (count[8] == 1'b1)
                tx_data_r_shift <= {tx_data_r_shift[22:0], 1'b0};
            else
                tx_data_l_shift <= {tx_data_l_shift[22:0], 1'b0};
        end
        
    always@(count, tx_data_l_shift, tx_data_r_shift)
        if (count[7:3] <= 5'd24 && count[7:3] >= 4'd1)
            if (count[8] == 1'b1)
                tx_sdout = tx_data_r_shift[23];
            else
                tx_sdout = tx_data_l_shift[23];
        else
            tx_sdout = 1'b0;
        
    /* SYNCHRONIZE DATA IN TO AXIS CLOCK DOMAIN */
    reg [2:0] din_sync_shift = 3'd0;
    wire din_sync = din_sync_shift[2];
    always@(posedge axis_clk)
        din_sync_shift <= {din_sync_shift[1:0], rx_sdin};
        
    /* I2S RECEIVE SHIFT REGISTERS */
    reg [23:0] rx_data_l_shift = 24'b0;
    reg [23:0] rx_data_r_shift = 24'b0;
    always@(posedge axis_clk)
        if (count[2:0] == 3'b011 && count[7:3] <= 5'd24 && count[7:3] >= 5'd1)
            if (lrck == 1'b1)
                rx_data_r_shift <= {rx_data_r_shift, din_sync};
            else
                rx_data_l_shift <= {rx_data_l_shift, din_sync};
    
    /* AXIS MASTER CONTROLLER */
    reg [31:0] rx_data_l = 32'b0;
    reg [31:0] rx_data_r = 32'b0;
    always@(posedge axis_clk)
        if (axis_resetn == 1'b0) begin
            rx_data_l <= 32'b0;
            rx_data_r <= 32'b0;
        end else if (count == EOF_COUNT && rx_axis_m_valid == 1'b0) begin
            rx_data_l <= {8'b0, rx_data_l_shift};
            rx_data_r <= {8'b0, rx_data_r_shift};
        end
        
    assign rx_axis_m_data = (rx_axis_m_last == 1'b1) ? rx_data_r : rx_data_l;
        
    always@(posedge axis_clk)
        if (axis_resetn == 1'b0)    
            rx_axis_m_valid <= 1'b0;
        else if (count == EOF_COUNT && rx_axis_m_valid == 1'b0)
            rx_axis_m_valid <= 1'b1;
        else if (rx_axis_m_valid == 1'b1 && rx_axis_m_ready == 1'b1 && rx_axis_m_last == 1'b1)
            rx_axis_m_valid <= 1'b0;
    
    always@(posedge axis_clk)
        if (axis_resetn == 1'b0)
            rx_axis_m_last <= 1'b0;
        else if (count == EOF_COUNT && rx_axis_m_valid == 1'b0)
            rx_axis_m_last <= 1'b0;
        else if (rx_axis_m_valid == 1'b1 && rx_axis_m_ready == 1'b1)
            rx_axis_m_last <= ~rx_axis_m_last;
        
        
endmodule
```
### 6. 將所有設計檔案進行整合 (top.v)
```
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// Description: Real-time processing of audio from Line In to Line Out of a Pmod
//              I2S2 on port JA
// 
//////////////////////////////////////////////////////////////////////////////////


module top #(
	parameter NUMBER_OF_SWITCHES = 4,
	parameter RESET_POLARITY = 0
) (
    input wire       clk,
    input wire [NUMBER_OF_SWITCHES-1:0] sw,
    input wire       reset,
    
    output wire tx_mclk,
    output wire tx_lrck,
    output wire tx_sclk,
    output wire tx_data,
    output wire rx_mclk,
    output wire rx_lrck,
    output wire rx_sclk,
    input  wire rx_data
);
    reg axis_clk;
    
    wire [23:0] axis_tx_data;
    wire axis_tx_valid;
    wire axis_tx_ready;
    wire axis_tx_last;
    
    wire [23:0] axis_rx_data;
    wire axis_rx_valid;
    wire axis_rx_ready;
    wire axis_rx_last;

	wire resetn = (reset == RESET_POLARITY) ? 1'b0 : 1'b1;

    reg [2:0] count;    
    always @(posedge clk) begin
        count <= count + 1;
        if(count == 3) begin
            count <= 0;
            axis_clk <= ~axis_clk;
        end
    end

    axis_i2s2 m_i2s2 (
        .axis_clk(axis_clk),
        .axis_resetn(resetn),
    
        .tx_axis_s_data(axis_tx_data),
        .tx_axis_s_valid(axis_tx_valid),
        .tx_axis_s_ready(axis_tx_ready),
        .tx_axis_s_last(axis_tx_last),
    
        .rx_axis_m_data(axis_rx_data),
        .rx_axis_m_valid(axis_rx_valid),
        .rx_axis_m_ready(axis_rx_ready),
        .rx_axis_m_last(axis_rx_last),
        
        .tx_mclk(tx_mclk),
        .tx_lrck(tx_lrck),
        .tx_sclk(tx_sclk),
        .tx_sdout(tx_data),
        .rx_mclk(rx_mclk),
        .rx_lrck(rx_lrck),
        .rx_sclk(rx_sclk),
        .rx_sdin(rx_data)
    );
     
    // dual_channel_fir_filter #(
    single_channel_fir_filter #(
		.DATA_WIDTH(24)
	) m_fir (
        .clk(axis_clk),
        .sw(sw),
        
        .s_axis_data(axis_rx_data),
        .s_axis_valid(axis_rx_valid),
        .s_axis_ready(axis_rx_ready),
        .s_axis_last(axis_rx_last),
        
        .m_axis_data(axis_tx_data),
        .m_axis_valid(axis_tx_valid),
        .m_axis_ready(axis_tx_ready),
        .m_axis_last(axis_tx_last)
    );
endmodule
```
### 7. 撰寫單通道所需的 Testbench 進行測試 (single_channel_fir_engine_tb.sv)
```
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023 Syed Fahad
//
// Create Date: 01/29/2023 05:13:37 PM
// Project Name: Real-time Audio Processing through FIR filters on Basys-3
// Description: Testbench for single-channel FIR filter backend
//
//////////////////////////////////////////////////////////////////////////////////


module single_channel_fir_filter_tb;
  reg clk;
  reg [3:0] sw;
  reg new_packet;
  reg signed [23:0] input_data [1:0];
  wire signed [23:0] output_data;
  wire signed [24-1:0] buffer [89-1:0];  // Buffer for data
  wire signed [16+24-1:0] op_buffer;

  wire [2:0] selected_filter;

  // Instantiate the fir_filter module
  single_channel_fir_engine uut (
    .clk(clk),
    .sw(sw),
    .new_packet(new_packet),
    .input_data(input_data),
    .output_data(output_data),
    .buffer(buffer),
    .op_buffer(op_buffer),
    .selected_filter(selected_filter)
  );

  // Clock generation for the testbench
  initial begin
    input_data[0] <= 24'h000000;
    input_data[1] <= 24'h000000;
    new_packet <= 1;
    sw <= 4'b0001;
    clk = 0;
    forever #1 clk = ~clk;
  end

  // Initialize inputs for the test
  initial begin
    // Set up initial values for inputs
    #178.5
    input_data[0] <= 24'hF00000;
    input_data[1] <= 24'h00000F;
    #1
    input_data[0] <= 24'h000000;
    input_data[1] <= 24'h000000;
    
    // TODO Add more data...
  end
endmodule
```
### 8. 撰寫雙通道所需的 Testbench 進行測試 (dual_channel_fir_filter_tb.sv)
```
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023 Syed Fahad
//
// Create Date: 01/29/2023 05:13:37 PM
// Project Name: Real-time Audio Processing through FIR filters on Basys-3
// Description: Testbench for dual-channel FIR filter backend
//
//////////////////////////////////////////////////////////////////////////////////


module dual_channel_fir_filter_tb;
  reg clk;
  reg [3:0] sw;
  reg new_packet;
  reg [23:0] input_data [1:0];
  wire [23:0] output_data [1:0];
  wire [24-1:0] buffer [45-1:0][1:0];  // Buffer for data
  wire [16+24-1:0] op_buffer [1:0];

  wire [2:0] selected_filter;

  // Instantiate the fir_filter module
  dual_channel_fir_engine uut (
    .clk(clk),
    .sw(sw),
    .new_packet(new_packet),
    .input_data(input_data),
    .output_data(output_data),
    .buffer(buffer),
    .op_buffer(op_buffer),
    .selected_filter(selected_filter)
  );

  // Clock generation for the testbench
  initial begin
    input_data[0] <= 24'h000000;
    input_data[1] <= 24'h000000;
    new_packet <= 1;
    sw <= 4'b0010;
    clk = 0;
    forever #1 clk = ~clk;
  end

  // Initialize inputs for the test
  initial begin
    // Set up initial values for inputs
    #90.5
    input_data[0] <= 24'h100000;
    input_data[1] <= 24'hF0000F;
    #1
    input_data[0] <= 24'h000000;
    input_data[1] <= 24'h000000;
    
    // TODO Add more data...
  end
endmodule
```