This is a follow up on the previews article on the low and high pass filter designer Low and High pass filter designer for implementation in VHDL.
When doing signal processing it happens quite often that one needs to changed the sample rate of a data stream in order to use it for further processing or simply reduce the amount of data which has to be transported.
The reduction of the data rate is called decimation. Decimation in principle means that one is removing a certain amount of samples from the signal and can also be implemented in this way. But one should lowpass filter the original signal before decimation in order to make sure that it is compatible with the new Nyquist rate.
This article is about interpolation and its implementation in VHDL. Interpolation can be performed by inserting a certain amount of zeros between the data sample points and then low pass filtering the new set of points with a cutoff at the old Nyquist rate.
This means if doing an interpolation by two, one adds a zero between every sample
S, 0, S, 0, S, 0, S, 0, S ...
Interpolation by 3 adds two 0 between each sample and so on.
Starting with a signal where each sample point is repeated by the interpolation factor will result in an aliased signal like the following which is not good and therefore interpolation is needed:
Adding zeros in between the sample points. Here an interpolation of 15 is performed:
This curve is low pass filtered with a filter with a cutoff frequency not higher than the Nyquist frequency of the old data sample: Since zeros are added by filtering with a filter kernel, normalized to one the amplitude of the interpolated signal will be lowered by the interpolation factor! To avoid this the filter kernel should be normalized to the interpolation factor. This means in practice it gets a gain.
This is shown in the following, on the left the interpolated wave with a filter gain of 1, on the right the same wave filtered with a gain of 15 (interpolation was 15).
It can be seen in the wave with gain the amplitude stays the same as the original wave, but the wave gets much smoother compared to the one where simply every sample was repeated a certain times. The interpolated curve starts a bit later compared to the original one, this comes from the delay introduced by the filter!
All plots were produced after the data was send through the VHDL test bench simulated in Model Sim.
The interpolation block is written in VHDL making use of the low pass filter code developed for the previous high/low pass filter project. Some of the filter code was updated in order to, for example being able to place the filter kernel outside of the block. This gives some more flexibility.
The interpolation block in VHDL:
library work; use work.myFilter_pkg.all; 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 Interpolation is generic ( --Do not change this g_fixInt, g_fixDec without changing -- also the package definition -- quartus does not support VHDL 2008 arrays with two unconstraint sizes 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 g_Interpolation :integer := 3 --Interploation factor max is 31 ); port ( clk : in std_logic; reset : in std_logic; FilterPole : in Filter_type(0 to g_FilterCoef-1);--array with all filter poles 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 Interpolation is 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; Filter : in Filter_type(0 to g_FilterCoef-1);--array with all filter poles 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 COMPONENT; signal FilterInput: STD_LOGIC_VECTOR(g_InputBits-1 DOWNTO 0); begin --signal FilterOutput: STD_LOGIC_VECTOR(g_InputBits-1 DOWNTO 0); i1 : Filter generic map( g_fixInt =>g_fixInt, --N bits infront of the point g_fixDec =>g_fixDec, --N bits after the point g_FilterCoef => g_FilterCoef, -- Number of filter coefficients --these must be smaller than g_fixInt g_InputBits =>g_InputBits, --Number of bits in the input std_logic_vector g_OutputBits =>g_OutputBits --Number of bits in the output std_logic_vector ) PORT MAP ( -- list connections between master ports and signals clk => clk, reset => reset, Filter => FilterPole, signal_in_int => FilterInput, signal_out_int => signal_out_int ); Interpolation_process: process(clk, reset) variable IntpCounter : integer range 0 to 31 := 0;--(others => 0); --can count to 31 begin if(reset='0') then IntpCounter:=0; elsif(rising_edge(clk)) then IntpCounter:=IntpCounter+1; if(IntpCounter=g_Interpolation) then IntpCounter:=0; end if; if(IntpCounter=0) then--if the counter is 0 a data sample goes into the output, FilterInput<=signal_in_int; else -- otherwise a 0 goes into the output FilterInput<=(others => '0'); end if; end if; end process Interpolation_process; end rtl;
What the code does, it interfaces the low pass filter and adds between each sample point a certain amount of zeros before sending the new sample to the low pass filter. The clock speed of this block should correspond to the new sample rate!
The output of this block is the new interpolated signal.
A test bench was written in order to test his package.
library work; use work.myFilter_pkg.all; library ieee; use ieee.std_logic_1164.all; use ieee.fixed_pkg.all; use ieee.std_logic_signed.all; use ieee.numeric_std.all; USE ieee.std_logic_textio.all; USE std.textio.all; ENTITY Interpolation_vhd_tst IS END Interpolation_vhd_tst; ARCHITECTURE Interpolation_arch OF Interpolation_vhd_tst IS -- constants constant g_fixInt : integer := 10; --N bits infront of the point constant g_fixDec : integer := 12; --N bits after the point constant g_FilterCoef : integer := 21; -- Number of filter coefficients --these must be smaller than g_fixInt constant g_InputBits : integer := 8; --Number of bits in the input std_logic_vector constant g_OutputBits : integer := 8; --Number of bits in the output std_logic_vector constant g_Interpolation :integer := 5; --Interploation factor max is 31 -- signals SIGNAL clk : STD_LOGIC :='0'; SIGNAL clk_slow : STD_LOGIC :='0'; SIGNAL FilterPole : Filter_type(0 to g_FilterCoef-1); SIGNAL reset : STD_LOGIC; SIGNAL signal_in_int : STD_LOGIC_VECTOR(g_InputBits-1 DOWNTO 0); SIGNAL signal_out_int : STD_LOGIC_VECTOR(g_OutputBits-1 DOWNTO 0); SIGNAL wave_out : std_LOGIC_VECTOR(7 downto 0); FILE out_file : TEXT OPEN WRITE_MODE IS "out_values.txt"; signal myFilter : Filter_type(0 to g_FilterCoef-1) := ( 0 =>to_sfixed (0.003295, g_fixInt-1,-1*g_fixDec), 1 =>to_sfixed (0.015601, g_fixInt-1,-1*g_fixDec), 2 =>to_sfixed (0.042085, g_fixInt-1,-1*g_fixDec), 3 =>to_sfixed (0.088530, g_fixInt-1,-1*g_fixDec), 4 =>to_sfixed (0.158500, g_fixInt-1,-1*g_fixDec), 5 =>to_sfixed (0.250302, g_fixInt-1,-1*g_fixDec), 6 =>to_sfixed (0.355226, g_fixInt-1,-1*g_fixDec), 7 =>to_sfixed (0.458193, g_fixInt-1,-1*g_fixDec), 8 =>to_sfixed (0.541004, g_fixInt-1,-1*g_fixDec), 9 =>to_sfixed (0.587264, g_fixInt-1,-1*g_fixDec), 10 =>to_sfixed (0.587264, g_fixInt-1,-1*g_fixDec), 11 =>to_sfixed (0.541004, g_fixInt-1,-1*g_fixDec), 12 =>to_sfixed (0.458193, g_fixInt-1,-1*g_fixDec), 13 =>to_sfixed (0.355226, g_fixInt-1,-1*g_fixDec), 14 =>to_sfixed (0.250302, g_fixInt-1,-1*g_fixDec), 15 =>to_sfixed (0.158500, g_fixInt-1,-1*g_fixDec), 16 =>to_sfixed (0.088530, g_fixInt-1,-1*g_fixDec), 17 =>to_sfixed (0.042085, g_fixInt-1,-1*g_fixDec), 18 =>to_sfixed (0.015601, g_fixInt-1,-1*g_fixDec), 19 =>to_sfixed (0.003295, g_fixInt-1,-1*g_fixDec), 20 =>to_sfixed (-0.000000, g_fixInt-1,-1*g_fixDec) ); COMPONENT Interpolation 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 g_Interpolation :integer --Interploation factor max is 31 ); PORT ( clk : IN STD_LOGIC; FilterPole : IN Filter_type(0 to g_FilterCoef-1); reset : IN STD_LOGIC; signal_in_int : IN STD_LOGIC_VECTOR(g_InputBits DOWNTO 0); signal_out_int : OUT STD_LOGIC_VECTOR(g_OutputBits 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 : Interpolation generic map( g_fixInt =>g_fixInt, --N bits infront of the point g_fixDec =>g_fixDec, --N bits after the point g_FilterCoef => g_FilterCoef, -- Number of filter coefficients --these must be smaller than g_fixInt g_InputBits =>g_InputBits, --Number of bits in the input std_logic_vector g_OutputBits =>g_OutputBits, --Number of bits in the output std_logic_vector g_Interpolation => g_Interpolation ) PORT MAP ( -- list connections between master ports and signals clk => clk, FilterPole => myFilter, 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_slow, reset => reset, wave_out => wave_out ); clk<=not clk after 50 ns; clk_slow<=not clk_slow after g_Interpolation*50 ns; signal_in_int<=wave_out; init : PROCESS -- variable declarations BEGIN -- code that executes only once 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 interpolation_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 interpolation_test; BEGIN -- code executes for every event on sensitivity list p_stable; interpolation_test(3072); --add a clock divided by three for the input wave signal --make control plot without the filter assert false report "--No Failure at the end of the sim1--" severity failure; --WAIT; END PROCESS always; END Interpolation_arch;
In the test bench the filter kernel needed for the interpolation is handed to the filter.
The code can be found in the same repository as the previous project.
To get the code:
git clone https://github.com/digibird1/FIRFilter
Leave a comment