Interpolation implementation in VHDL

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:

Interpol15_TimeInput

Adding zeros in between the sample points. Here an interpolation of 15 is performed:
Interpol15Filled0_TimeOutput

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).

Interpol15NoGain_TimeOutput Interpol15WithGain_TimeOutput

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