Low and High pass filter designer for implementation in VHDL

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:

FIR_FilterDesigner_Filter_LPF FIR_FilterDesigner_Filter_HPF

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:

FIR_FFTInput FIR_TimeInput

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.

FIR_LPF21_FFTOutput FIR_LPF21_TimeOutput

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

FIR_LPF201_FFTOutput FIR_LPF201_TimeOutput

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.

FIR_HPF_FFTOutput FIR_HPF_TimeOutput

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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: