FIR Filter Design in Vivado, Tested with a DDS Frequency Sweep

Introduction

In this project, I demonstrate how to design an FIR filter using the Vivado FIR IP Core and validate the design using a frequency sweep generated by the Xilinx DDS IP Core. The DDS performs a sweep from DC to 100 kHz. More information about how DDS works and implemented in Vivado can be found in my previous posts.

The low pass filter to be design is a 12.5KHz filter and it is widely used for Narrow Band FM radios.

FIR Filter Requirements

The FIR filter must meet the following specifications:

  • Sampling frequency: 1.5625 MHz
  • Cutoff frequency: 12.5 kHz
  • Stopband frequency: 50 kHz
  • Stopband ripple / attenuation: 60 dB

Implementation

To obtain the FIR filter coefficients, the MATLAB Filter Designer tool was used. The following settings were applied to generate the coefficients.

Figure 1: MATLAB Filter Designer Settings

The tool generated 82 FIR coefficients, which were then imported into the Vivado FIR IP Core to implement the digital low-pass filter.

FIR Coefficients

Below are the FIR filter coefficients generated by the MATLAB Filter Designer and used in the Vivado FIR IP Core:

Coeff 1Coeff 2Coeff 3Coeff 4
-0.000967862-0.0000481134-0.000004662350.0000726547
0.0001885820.0003467090.0005527220.000811463
0.0011276830.0015062780.0019518620.002468238
0.0030589890.0037264250.0044722870.005297174
0.006200740.0071812250.008235950.009360603
0.0105499980.0117974380.0130951980.014434148
0.0158043380.0171944530.0185927390.019986384
0.0213623360.0227070650.0240072130.02524909
0.0264197720.0275060820.0284959940.029378269
0.0301430460.0307813370.0312868520.031651157
0.0318711980.0319459670.0318711980.031651157
0.0312868520.0307813370.0301430460.029378269
0.0284959940.0275060820.0264197720.02524909
0.0240072130.0227070650.0213623360.019986384
0.0185927390.0171944530.0158043380.014434148
0.0130951980.0117974380.0105499980.009360603
0.008235950.0071812250.006200740.005297174
0.0044722870.0037264250.0030589890.002468238
0.0019518620.0015062780.0011276830.000811463
0.0005527220.0003467090.0001885820.0000726547
-0.00000466235-0.0000481134-0.000967862

Vivado FIR Compiler Setup

Vivado 2020.2 was used to configure and generate the FIR filter using the Xilinx FIR Compiler IP Core. From the IP Catalog, the FIR Compiler IP was selected and configured according to the previously defined filter requirements. The following screenshots illustrate the configuration process.

The screenshot below shows how the FIR coefficients are entered into the Coefficient Vector field. The coefficients are comma-separated, and a total of 83 coefficients were used.

Figure 2: FIR Filter Options Setup
Figure 3: FIR Channel Specifications
Figure 4: FIR Implementation Setup (1 of 2)
Figure 5: FIR Implementation Setup (2 of 2)
Figure 6: FIR Detailed Implementation (1 of 2)
Figure 7: FIR Detailed Implementation (2 of 2)
Figure 8: FIR Filter Details Summary

VHDL Implementation

Below is the top-level VHDL design used to implement the system. The design instantiates the following main digital blocks:

  • Clock Wizard
  • FIR Compiler IP Core
  • DDS IP Core

The Clock Wizard generates a 100 MHz system clock from the on-board 125 MHz oscillator. This 100 MHz clock is used by both the FIR Compiler and DDS IP cores. The effective sampling frequency used by the FIR filter is 1.5625 MHz.

VHDL Top-Level Implementation

Below is the complete VHDL top-level design used to integrate the Clock Wizard, DDS sweep generator, and FIR Compiler IP core. This module handles clock generation, sample-rate control, and data flow between the DDS and FIR filter.


library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
library UNISIM;
use UNISIM.vcomponents.all;
entity FIR_TopLevel is
    port (
        clk_p   : in  std_logic;
        clk_n   : in  std_logic;
        reset   : in  std_logic;                 -- Reset signal
        FIR_Out : out std_logic_vector(15 downto 0)
    );
end FIR_TopLevel;
architecture Behavioral of FIR_TopLevel is
    signal clk                 : std_logic := '0';
    signal clk_aux             : std_logic := '0';
    signal Clk_6p25MHz          : std_logic := '0';
    signal Clk_100MHz           : std_logic := '0';
    signal lockedSig            : std_logic := '0';
    signal DDS_data_tvalid_out  : std_logic := '0';
    signal DDS_data_tdata_out   : std_logic_vector(15 downto 0);
    signal clockcounter         : integer range 0 to 4;
    signal FIR_sample_valid_in  : std_logic := '0';
    signal s_axis_data_tready_Sig : std_logic;
    signal m_axis_data_tvalid_Sig : std_logic;
    signal m_axis_data_tdata_Sig  : std_logic_vector(39 downto 0);
    component clk_wiz_0
        port (
            clk_out1 : out std_logic;
            clk_out2 : out std_logic;
            locked   : out std_logic;
            clk_in1  : in  std_logic
        );
    end component;
    component DDS_Sweep
        port (
            clk             : in  std_logic := '0';
            reset           : in  std_logic;
            DDS_data_tvalid : out std_logic;
            DDS_data_tdata  : out std_logic_vector(15 downto 0)
        );
    end component;
    component fir_compiler_0
        port (
            aclk               : in  std_logic;
            s_axis_data_tvalid : in  std_logic;
            s_axis_data_tready : out std_logic;
            s_axis_data_tdata  : in  std_logic_vector(15 downto 0);
            m_axis_data_tvalid : out std_logic;
            m_axis_data_tdata  : out std_logic_vector(39 downto 0)
        );
    end component;
begin
    -- Differential clock input buffer (125 MHz)
    ibufds_inst : IBUFDS
        port map (
            I  => clk_p,
            IB => clk_n,
            O  => clk_aux
        );
    -- Global clock buffer
    clk_buf : BUFG
        port map (
            I => clk_aux,
            O => clk
        );
    -- Clock Wizard instance
    ClockWiZ : clk_wiz_0
        port map (
            clk_out1 => Clk_6p25MHz,
            clk_out2 => Clk_100MHz,
            locked   => lockedSig,
            clk_in1  => clk
        );
    -- DDS sweep generator
    DDS_Inst : DDS_Sweep
        port map (
            clk             => Clk_100MHz,
            reset           => reset,
            DDS_data_tvalid => DDS_data_tvalid_out,
            DDS_data_tdata  => DDS_data_tdata_out
        );
    -- FIR Compiler IP core
    FIR_IPcore : fir_compiler_0
        port map (
            aclk               => Clk_100MHz,
            s_axis_data_tvalid => FIR_sample_valid_in,
            s_axis_data_tready => s_axis_data_tready_Sig,
            s_axis_data_tdata  => DDS_data_tdata_out,
            m_axis_data_tvalid => m_axis_data_tvalid_Sig,
            m_axis_data_tdata  => m_axis_data_tdata_Sig
        );
    -- Output assignment (scaled FIR output)
    FIR_Out <= m_axis_data_tdata_Sig(39 downto 24);
    -- Generate FIR sample-valid signal at 1.5625 MHz
    process (Clk_6p25MHz, reset)
    begin
        if reset = '1' then
            clockcounter        <= 0;
            FIR_sample_valid_in <= '0';
        elsif rising_edge(Clk_6p25MHz) then
            clockcounter <= clockcounter + 1;
            if clockcounter = 3 then
                FIR_sample_valid_in <= '1';
                clockcounter        <= 0;
            else
                FIR_sample_valid_in <= '0';
            end if;
        end if;
    end process;
end Behavioral;
          	

Vivado Simulation and Filter Validation With DDS

Vivado simulation tool was used to validate the frequency response of the filter. With the help of Xilinix DDS that performed a frequency sweep from DC to 100KHz, we could see in the image taken below that input frequency coming in from DDS into the FIR filter starts to attenuate roughly around 13KHz.

Figure 9: Simulation of the FIR output; Input is a frequency sweep from 0-100KHz

In the next project, I plan to demonstrate the performance of this filter in the real world using and signal generator and an FPGA development board.

For access to the source code, you can clone a copy from my Github repository in the link below.

Source code:

https://github.com/faridfma/FIR_12K5_DDS

I hope you will find this tutorial helpful, and I would really appreciate any feedback or comments in the comments section and via emails.