Working with radio communication or audio signals sooner or later filters are needed. These can be build in the analog domain but depending on the application also in the digital domain.

This project is about designing a high or low pass filter in the digital domain and the implementation as a FIR (Finite Impulse Response) Filter in VHDL.

The aim was to have a design tool where one can specify the cutoff frequency, the bandwidth of the transition zone and gets back the filter coefficients which one can copy in a VHDL template, to get the filter into an FPGA.

A VHDL test bench was developed in order to be able to do tests on the VHDL filter implementation.

The filter is processed with the signal by using convolution.

For a theoretical explanation I suggest the book “The Scientist and Engineer’s Guide to

Digital Signal Processing” [1].

The filter designer is written in the MATLAB clone OCTAVE [2] a powerful open source project which has more or less the same syntax as the original MATLAB.

The filter designer code looks the following way:

function FilterDesigner CutOffFreq=20;%change in Hz, max 1/2 of the sample frequency SampleRate=100;%Sample Rate %Bandwidth of the filter BW=0.02;%Change MakeHighPass=0; %0 Low pass, 1 High Pass

In order to calculate the filter coefficients the first step is to specify the cutoff frequency, the sample rate of the signal to be filtered and the band width, how fast the filter is falling from pass-band to stop-band.

It can also be decided if the filter is supposed to be a low pass filter (LPF) or a high pass filter (HPF).

%Length of filter Kernel M=round(4/BW)+1; Filter=zeros(1,M); %Normalized cutoff frequency FC=CutOffFreq/SampleRate %Cut off frequency change for High pass if(MakeHighPass==1) FC=0.5-FC; endif %Calculate the Filter Kernel for a = 1:M if ((a)-M/2==0) Filter(a)=2*pi*FC; else Filter(a)=sin(2*pi*FC*((a)-M/2))/((a)-M/2); endif %Blackman window Filter(a)=Filter(a)*(0.42-0.5*cos(2*pi*(a)/M)+0.08*cos(4*pi*(a)/M)); endfor %Normalize the Filter Sum=0; for i = 1:M Sum=Sum+Filter(i); endfor for i = 1:M Filter(i)=Filter(i)/Sum; endfor %Convert filter from low pass to high pass if(MakeHighPass==1) %change every sencond sign to make the filter a high pass for a = 1:2:M Filter(a)=-1*Filter(a); endfor endif;

From these information the code determines how long the filter kernel needs to be, generates a sinc functions and windows the function with the Blackman window to make it smooth going down to zero at the etches.

The filter kernel is normalized to one in order to not manipulate the amplitudes of the pass band frequencies.

figure(1); subplot(3,1,1); plot(1:M,Filter); grid on; title ("Filter Kernel"); %xlabel ("Samples"); ylabel ("Amplitude"); subplot(3,1,2); FFTSize=2048; myFFT2=fft(Filter,FFTSize); Real=myFFT2(1:FFTSize/2); plot(0:0.5/(FFTSize/2):0.5-0.5/(FFTSize/2),abs(Real)); grid on; title ("Filter Performance (Norm frequency)"); %xlabel ("Samples"); ylabel ("lin Amplitude"); subplot(3,1,3); plot(0:0.5/(FFTSize/2):0.5-0.5/(FFTSize/2),20*log10(abs(Real))); grid on; title ("Filter Performance (Norm frequency)"); %xlabel ("Samples"); ylabel ("log Amplitude [dB]"); saveas (1, "FilterDesigner_Filter.png"); %Print the Filter Kernel List as VHDL %0 =>to_sfixed (3.125, g_fixInt-1,-1*g_fixDec) for i = 1:M printf ("%d =>to_sfixed (%f, g_fixInt-1,-1*g_fixDec),\n",i-1,Filter(i)); endfor

The last part produces a plot showing the filter in the time and the frequency domain.

The frequency domain is shown in linear and in logarithmic form.

The filter coefficients are printed out in the following form:

0 =>to_sfixed (0.000000, g_fixInt-1,-1*g_fixDec),

This form of code can be directly copied into the VHDL code template explained later on.

An example of a low pass filter design (left) and a high pass filter (right) design is given in the following:

In order to apply the code by convolution on the signal some VHDL code was developed:

library ieee; use ieee.std_logic_1164.all; use ieee.fixed_pkg.all; use ieee.std_logic_signed.all; use ieee.numeric_std.all; entity Filter is generic ( --g_Variable : integer := 10 g_fixInt : integer := 10; --N bits infront of the point g_fixDec : integer := 12; --N bits after the point g_FilterCoef : integer := 21; -- Number of filter coefficients --these must be smaller than g_fixInt g_InputBits : integer := 8; --Number of bits in the input std_logic_vector g_OutputBits : integer := 8 --Number of bits in the output std_logic_vector ); port ( clk : in std_logic; reset : in std_logic; signal_in_int : in std_logic_vector(g_InputBits-1 downto 0); signal_out_int : out std_logic_vector(g_OutputBits-1 downto 0) ); end entity; architecture rtl of Filter is type Filter_type is array (0 to g_FilterCoef-1) of sfixed (g_fixInt-1 downto -1*g_fixDec); type IterBuffer is array (0 to g_FilterCoef-1) of sfixed (g_fixInt-1 downto -1*g_fixDec); --Place here the list of coefficients (Filter Kernel) signal Filter : Filter_type := ( 0 =>to_sfixed (-0.000164, g_fixInt-1,-1*g_fixDec), 1 =>to_sfixed (-0.001286, g_fixInt-1,-1*g_fixDec), 2 =>to_sfixed (0.000000, g_fixInt-1,-1*g_fixDec), 3 =>to_sfixed (0.008320, g_fixInt-1,-1*g_fixDec), 4 =>to_sfixed (0.010323, g_fixInt-1,-1*g_fixDec), 5 =>to_sfixed (-0.019086, g_fixInt-1,-1*g_fixDec), 6 =>to_sfixed (-0.054476, g_fixInt-1,-1*g_fixDec), 7 =>to_sfixed (0.000000, g_fixInt-1,-1*g_fixDec), 8 =>to_sfixed (0.185697, g_fixInt-1,-1*g_fixDec), 9 =>to_sfixed (0.370673, g_fixInt-1,-1*g_fixDec), 10 =>to_sfixed (0.370673, g_fixInt-1,-1*g_fixDec), 11 =>to_sfixed (0.185697, g_fixInt-1,-1*g_fixDec), 12 =>to_sfixed (0.000000, g_fixInt-1,-1*g_fixDec), 13 =>to_sfixed (-0.054476, g_fixInt-1,-1*g_fixDec), 14 =>to_sfixed (-0.019086, g_fixInt-1,-1*g_fixDec), 15 =>to_sfixed (0.010323, g_fixInt-1,-1*g_fixDec), 16 =>to_sfixed (0.008320, g_fixInt-1,-1*g_fixDec), 17 =>to_sfixed (0.000000, g_fixInt-1,-1*g_fixDec), 18 =>to_sfixed (-0.001286, g_fixInt-1,-1*g_fixDec), 19 =>to_sfixed (-0.000164, g_fixInt-1,-1*g_fixDec), 20 =>to_sfixed (-0.000000, g_fixInt-1,-1*g_fixDec) ); --End list of coefficients signal signal_in : sfixed (g_fixInt-1 downto -1*g_fixDec); signal signal_out : sfixed (g_fixInt-1 downto -1*g_fixDec); signal signal_out_tmp : sfixed (g_fixInt-1 downto -1*g_fixDec); begin Filter_process: process(clk, reset) variable myBuffer : IterBuffer; variable signal_tmp : sfixed (2*g_fixInt downto -2*g_fixDec); begin if(reset='0') then for I in 0 to g_FilterCoef-1 loop myBuffer(I):=to_sfixed (0.0, g_fixInt-1,-1*g_fixDec); end loop; elsif(rising_edge(clk)) then --Do the convolution --This part could be piplined for faster operation for I in 0 to g_FilterCoef-1 loop signal_tmp:=myBuffer(I)+signal_in*Filter(I); myBuffer(I):=signal_tmp(g_fixInt-1 downto -1*g_fixDec); end loop; signal_out_tmp<=myBuffer(0); for I in 0 to (g_FilterCoef-1)-1 loop--shift the buffer myBuffer(I):=myBuffer(1+I); end loop; myBuffer(g_FilterCoef-1):=to_sfixed (0.0, g_fixInt-1,-1*g_fixDec);--Add 0; end if; end process Filter_process; --Make sure the output comes at the clock edge Output_process: process(clk) begin if(rising_edge(clk)) then signal_out<=signal_out_tmp; end if; end process Output_process; signal_in<=to_sfixed (signed(signal_in_int), g_fixInt-1,-1*g_fixDec); signal_out_int<='0' &(g_outputBits-2 downto 0 => '1') when signal_out > 2**(g_outputBits-1) else --max allowed by #bits '1' & (g_outputBits-2 downto 0 => '0') when signal_out < -1*2**(g_outputBits-1) else --min allowed by #bits std_logic_vector(signal_out(g_outputBits-1 downto 0)); end rtl;

The VHDL code has some generic variable which give some flexibility for example in the bit length of the in and outputs, as well as the filter kernel coefficients.

The in- and outputs are signed integer numbers which are handed in as `std_logic_vectors`

.

In order to increase the precision of the calculations the filter kernel is saved in fixed point form.

The convolution process is performed using fixed point numbers as well.

In order to be able to test the code and also in order to be able to study the effect of the fixed point rounding and integer conversion a test bench was developed. The test bench is used together with Model Sim to test the VHDL filter implementation.

The Modelsim scripts can be found on GITHub (see below).

The test bench:

LIBRARY ieee; USE ieee.std_logic_1164.all; ----------------------------------------------------------------- -- the following is added to support writing with write/writeline ----------------------------------------------------------------- USE ieee.std_logic_textio.all; USE std.textio.all; use ieee.std_logic_signed.all; use ieee.numeric_std.all; ENTITY Filter_vhd_tst IS END Filter_vhd_tst; ARCHITECTURE Filter_arch OF Filter_vhd_tst IS -- constants --these must be smaller than g_fixInt constant InputBits : integer :=8; --Number of bits in the input std_logic_vector constant OutputBits : integer :=8;--Number of bits in the output std_logic_vector -- signals SIGNAL clk : STD_LOGIC :='0'; SIGNAL reset : STD_LOGIC; SIGNAL signal_in_int : STD_LOGIC_VECTOR(InputBits-1 DOWNTO 0); SIGNAL signal_out_int : STD_LOGIC_VECTOR(OutputBits-1 DOWNTO 0); SIGNAL wave_out : std_LOGIC_VECTOR(7 downto 0); FILE out_file : TEXT OPEN WRITE_MODE IS "out_values.txt"; COMPONENT Filter generic ( g_fixInt : integer; --N bits infront of the point g_fixDec : integer; --N bits after the point g_FilterCoef : integer ; -- Number of filter coefficients --these must be smaller than g_fixInt g_InputBits : integer; --Number of bits in the input std_logic_vector g_OutputBits : integer --Number of bits in the output std_logic_vector ); PORT ( clk : IN STD_LOGIC; reset : IN STD_LOGIC; signal_in_int : IN STD_LOGIC_VECTOR(InputBits-1 DOWNTO 0); signal_out_int : OUT STD_LOGIC_VECTOR(OutputBits-1 DOWNTO 0) ); END COMPONENT; COMPONENT SignalFromLookUp PORT ( clk : IN STD_LOGIC; reset : IN STD_LOGIC; wave_out : OUT STD_LOGIC_VECTOR(7 DOWNTO 0) ); END COMPONENT; BEGIN i1 : Filter generic map( g_fixInt =>10, --N bits infront of the point g_fixDec =>12, --N bits after the point g_FilterCoef => 21, -- Number of filter coefficients --these must be smaller than g_fixInt g_InputBits =>InputBits, --Number of bits in the input std_logic_vector g_OutputBits =>OutputBits --Number of bits in the output std_logic_vector ) PORT MAP ( -- list connections between master ports and signals clk => clk, reset => reset, signal_in_int => signal_in_int, signal_out_int => signal_out_int ); i2 : SignalFromLookUp PORT MAP ( -- list connections between master ports and signals clk => clk, reset => reset, wave_out => wave_out ); clk<=not clk after 50 ns; signal_in_int<=wave_out; init : PROCESS -- variable declarations BEGIN -- code that executes only once --do not put here variabes whicha re also changed in the always process WAIT; END PROCESS init; always : PROCESS -- optional sensitivity list -- ( ) -- variable declarations procedure p_stable is begin --set all signals to a default value reset<='1'; end procedure p_stable; procedure filter_test(constant c_loop : integer) is variable l:line; variable i:integer :=0; begin loop1: while i <= c_loop loop wait until rising_edge(clk); i:=i+1; WRITE(l, i); WRITE(l, string'(" ")); WRITE(l, to_integer(signed(wave_out))); WRITE(l, string'(" ")); WRITE(l, to_integer(signed(signal_out_int))); WRITELINE(out_file, l); end loop loop1; end filter_test; BEGIN -- code executes for every event on sensitivity list p_stable; filter_test(2048); assert false report "--No Failure at the end of the sim--" severity failure; --WAIT; END PROCESS always; END Filter_arch;

The test bench connects a lookup table, containing 50 cos(x) waves spaced by 0.01 normalized frequencies, with the filter.

The input signal from the lookup table as well as the filtered signal is written into a file for further analysis.

This has the advantage that the input signal is is already limited by its bit resolution and the output signal as well.

The calculations are performed in fixed point notation, so the behavior should be the same as later on the FPGA.

The test signal looks the following:

as can be seen equally spaced amplitudes in the frequency spectrum. A piece of OCTAVE code to analyze the output from te test bench can also be found in GITHub.

The input signal filtered with 21 coefficient long low pass filter kernel is looks the following. The design fall off bandwidth (BW) of the design was 0.2.

The output signal filtered with a 201 long low pass filter kernel. The design fall off bandwidth (BW) of the design was 0.02.

It can be seen that by increasing the amount of coefficients the falloff between pass band and stop band gets much narrower.

This can be specified in the filter design as the bandwidth. The disadvantage is the longer the kernel gets the more computations have to be performed in order to get the signal corrected. Also on the FPGA this means that the design gets bigger and bigger. At some point more advanced convolution using FFT should be considered.

For a high pass filter with a BW of 0.05 results in 81 poles the design cutoff frequency was 0.2.

As can be seen lower frequencies are removed.

[1] http://www.dspguide.com/

[2] http://www.gnu.org/software/octave/

To get the code:

git clone https://github.com/digibird1/FIRFilter

Check out `commit 167aa0746729181aefa654a9544e3dc6cd10d037`

in order to get the code described in this article,

newer versions of the code contain slightly modified code.

## Leave a Reply