# 軟硬體協同設計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 ```