I am trying to implement an IIR second order in Direct FormII in VHDL. To make it easier to understand for me and to avoid former timing issues, I implemented the operations sequentially. In my application this will work because the filter frequency is much higher than the frequency I receive my samples with. Now, I want to test this second order structure as a simple Lowpass which cuts frequencys higher than 10kHz. Therefore, I am testing it with a sine of 200Hz and add a sine of 12,5kHz. My expectations would be that the 12,5kHz are eliminated and we only see the 200Hz at the output. The input comes from an ADC and the output is generated by a DAC. The protocol between ADC, Filter and DAC works as intended. The filter coefficients are from matlab. My output even shows a signal with 200Hz frequency, but the values are completely off. I suspect that I didn't handle the truncation correctly. I perform truncation at two spots, which are marked in the graphic I attached. There are also numbers marked in red. They show the sequential operations I am performing.
Truncate green, sequential operations red:
VHDL Code:
ibrary IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;
use IEEE.math_real.all;
entity IIR_DF2 is
-- generic(N : integer := 20); --(Anzahl der Koeffizienten - 1)
port
(
x_in : in std_logic_vector(11 downto 0); --Input 12 Bit vom AD Wandler
clk : in std_logic; --Input Clk mit hoher Frequenz
rst : in std_logic; --Reset Active Low
enable_data : in std_logic; --next Sample vom ADC
data_acknowledged : in std_logic; --DA hat Daten erhalten
error_from_ADC : in std_logic; --Errorsignal vom ADC
filter_rdy : out std_logic; --Signalisiere dem DA ready.
y : out std_logic_vector(11 downto 0) --Output 12 Bit an den DA - Wandler
);
end IIR_DF2;
--Test mit Tiefpass bis 10kHz
architecture IIR_DF2_arch of IIR_DF2 is
--States fuer sequentielle Berechnungen
type states is (
startup,
waitForADC,
readAndShift,
firstMulti,
secondMulti,
thirdMulti,
fourthMulti,
fifthMulti,
sixthMulti,
firstSums,
secondSums,
thirdSums,
finalSum,
shiftToDA
);
signal current_state : states := startup; --Enthaelt den aktuellen Status, initialisiert mit "startup"
--Gain mit Darstellung 2.14
constant gain : signed(15 downto 0) := X"0123";
--Nullstellen mit Darstellung 2.14
constant zero_b0 : signed(15 downto 0) := X"4000";
constant zero_b1 : signed(15 downto 0) := X"0378";
constant zero_b2 : signed(15 downto 0) := X"4000";
--Polstellen mit Darstellung 2.14
constant pole_a1 : signed(15 downto 0) := X"6DA4"; --Vorzeichen muss invertiert werden!!
constant pole_a2 : signed(15 downto 0) := X"D005"; --Vorzeichen muss invertiert werden!!
----------------------------------------
--Zwischenergebnisse--------------------
----------------------------------------
--Input und Output erweitert auf 1.12 signed
signal input : signed(12 downto 0) := (others => '0');
--Gain
signal scaled_input : signed(28 downto 0) := (others => '0'); --Input nach Gain 3.26
--Summen
signal sum_input : signed(28 downto 0) := (others => '0'); --Erste Summe nach Input ohne Delay 3.26
signal sum_output : signed(44 downto 0) := (others => '0'); --Summe vor Output 5.40
signal sum_a1_a2 : signed(28 downto 0) := (others => '0'); --Summe nach Multiplikation mit den Polstellen a1 und a2 3.26
signal sum_b1_b2 : signed(44 downto 0) := (others => '0'); --Summe nach Multiplikation mit den Nullstellen b1 und b2 5.40
--Multiplikationen (a0 immer = 1, daher keine Multiplikation)
signal b0_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b0 5.40
signal b1_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b1 5.40
signal b2_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b2 5.40
signal a1_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit a1 5.40
signal a2_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit a2 5.40
--Delays
signal first_delay : signed(28 downto 0) := (others => '0'); --erster Delay 3.26
signal second_delay : signed(28 downto 0) := (others => '0'); --zweiter Delay 3.26
--Zwischenstufe
signal a1_mult_truncate : signed(28 downto 0) := (others => '0');
signal a2_mult_truncate : signed(28 downto 0) := (others => '0');
--Rundungen
-- signal round_Top_Left : signed(15 downto 0);--Erstes Runden
begin
--Durchfuehren der Operationen abhaengig vom State
statemachine : process(clk, rst)
begin
if (rst = '0') then
current_state <= startup;
--Signalaenderung bei steigender Flanke des 125MHz Clocks
elsif (rising_edge(clk)) then
case (current_state) is
--Alles zuruecksetzen
when startup =>
if (error_from_ADC = '0') then
current_state <= waitForADC;
end if;
--Auf naechstes Sample warten
when waitForADC =>
if (enable_data = '1') then
current_state <= readAndShift;
end if;
--Neues sample einlesen und Zeitverschiebung
when readAndShift =>
input <= "0" & signed(x_in); --auf 13 Bit signed erweitern(immer positiv)
first_delay <= sum_input;
second_delay <= first_delay;
current_state <= firstMulti;
--Filteroperationen sequentiell durchfuehren
when firstMulti =>
scaled_input <= input * gain; --1.12 * 2.14 = 3.26
current_state <= secondMulti;
when secondMulti =>
a1_mult <= first_delay * pole_a1; --3.26 * 2.14 = 5.40
current_state <= thirdMulti;
when thirdMulti =>
a2_mult <= second_delay * pole_a2; --3.26 * 2.14 = 5.40
current_state <= fourthMulti;
when fourthMulti =>
b1_mult <= first_delay * zero_b1; --3.26 * 2.14 = 5.40
current_state <= fifthMulti;
when fifthMulti =>
b2_mult <= second_delay * zero_b2; --3.26 * 2.14 = 5.40
current_state <= firstSums;
when firstSums =>
-- sum_a1_a2 <= a1_mult(39 downto 11) + a2_mult(39 downto 11); --5.40 to 3.26 CHECKEN!!
-- sum_a1_a2 <= a1_mult(42 downto 14) + a2_mult(42 downto 14); --5.40 to 3.26 CHECKEN!!
a1_mult_truncate <= a1_mult(42 downto 14);
a2_mult_truncate <= a2_mult(42 downto 14);
sum_b1_b2 <= b1_mult + b2_mult; --5.40 + 5.40
current_state <= secondSums;
when secondSums =>
sum_a1_a2 <= a1_mult_truncate + a2_mult_truncate;
current_state <= thirdSums;
when thirdSums =>
sum_input <= sum_a1_a2 + scaled_input;
current_state <= sixthMulti;
when sixthMulti =>
b0_mult <= sum_input * zero_b0;
current_state <= finalSum;
when finalSum =>
sum_output <= b0_mult + sum_b1_b2;
current_state <= shiftToDA;
--Daten an DA uebermitteln
when shiftToDA =>
y <= std_logic_vector(sum_output(39 downto 28)); --WELCHE BITS GENAU PRUEFEN!! 5.40 zu 12 Bit
filter_rdy <= '1'; --DA auf Datenempfang vorbereiten
if (error_from_ADC = '1') then
current_state <= startup;
elsif (enable_data = '0' and data_acknowledged = '1') then
current_state <= waitForADC;
end if;
end case;
else
NULL;
end if;
end process statemachine;
end IIR_DF2_arch;
EDIT With my updated code, I receive a 200Hz signal as the output. The difference is in the truncation of the bits that I feed to the output. To be honest, I am unsure why it works now. But I am quite confused for three reasons:
1) The Vivado compiler tells me, that timing requirements are not met now. How can this occur when I am only changing the position of the bits that are fed to the output? Furthermore I thought that implementing the code sequentially would make it easier to meet timing requirements.
2) Why can I actually make it work on my FPGA when the timing requirements are not met?
3) Why is the output amplified when the gain response shows 0dB at 200Hz?
Updated Code:
library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;
use IEEE.math_real.all;
entity IIR_DF2 is
-- generic(N : integer := 20); --(Anzahl der Koeffizienten - 1)
port
(
x_in : in std_logic_vector(11 downto 0); --Input 12 Bit vom AD Wandler
clk : in std_logic; --Input Clk mit hoher Frequenz
rst : in std_logic; --Reset Active Low
enable_data : in std_logic; --next Sample vom ADC
data_acknowledged : in std_logic; --DA hat Daten erhalten
error_from_ADC : in std_logic; --Errorsignal vom ADC
filter_rdy : out std_logic; --Signalisiere dem DA ready.
y : out std_logic_vector(11 downto 0) --Output 12 Bit an den DA - Wandler
);
end IIR_DF2;
--Test mit Tiefpass bis 10kHz
architecture IIR_DF2_arch of IIR_DF2 is
--States fuer sequentielle Berechnungen
type states is (
startup,
waitForADC,
readAndShift,
firstMulti,
secondMulti,
thirdMulti,
fourthMulti,
fifthMulti,
sixthMulti,
firstSums,
truncate1,
truncate2,
secondSums,
thirdSums,
finalSum,
shiftToDA
);
signal current_state : states := startup; --Enthaelt den aktuellen Status, initialisiert mit "startup"
--Gain mit Darstellung 2.14
constant gain : signed(15 downto 0) := X"0123";
--Nullstellen mit Darstellung 2.14
constant zero_b0 : signed(15 downto 0) := X"4000";
constant zero_b1 : signed(15 downto 0) := X"0378";
constant zero_b2 : signed(15 downto 0) := X"4000";
--Polstellen mit Darstellung 2.14
constant pole_a1 : signed(15 downto 0) := X"6DA4"; --Vorzeichen muss invertiert werden!!
constant pole_a2 : signed(15 downto 0) := X"D005"; --Vorzeichen muss invertiert werden!!
----------------------------------------
--Zwischenergebnisse--------------------
----------------------------------------
--Input und Output erweitert auf 1.12 signed
signal input : signed(12 downto 0) := (others => '0');
--Gain
signal scaled_input : signed(28 downto 0) := (others => '0'); --Input nach Gain 3.26
--Summen
signal sum_input : signed(28 downto 0) := (others => '0'); --Erste Summe nach Input ohne Delay 3.26
signal sum_output : signed(44 downto 0) := (others => '0'); --Summe vor Output 5.40
signal sum_a1_a2 : signed(28 downto 0) := (others => '0'); --Summe nach Multiplikation mit den Polstellen a1 und a2 3.26
signal sum_b1_b2 : signed(44 downto 0) := (others => '0'); --Summe nach Multiplikation mit den Nullstellen b1 und b2 5.40
--Multiplikationen (a0 immer = 1, daher keine Multiplikation)
signal b0_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b0 5.40
signal b1_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b1 5.40
signal b2_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b2 5.40
signal a1_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit a1 5.40
signal a2_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit a2 5.40
--Delays
signal first_delay : signed(28 downto 0) := (others => '0'); --erster Delay 3.26
signal second_delay : signed(28 downto 0) := (others => '0'); --zweiter Delay 3.26
--Zwischenstufe
signal a1_mult_truncate : signed(28 downto 0) := (others => '0');
signal a2_mult_truncate : signed(28 downto 0) := (others => '0');
--Rundungen
-- signal round_Top_Left : signed(15 downto 0);--Erstes Runden
begin
--Durchfuehren der Operationen abhaengig vom State
statemachine : process(clk, rst)
begin
if (rst = '0') then
current_state <= startup;
--Signalaenderung bei steigender Flanke des 125MHz Clocks
elsif (rising_edge(clk)) then
case (current_state) is
--Alles zuruecksetzen
when startup =>
if (error_from_ADC = '0') then
current_state <= waitForADC;
end if;
--Auf naechstes Sample warten
when waitForADC =>
if (enable_data = '1') then
current_state <= readAndShift;
end if;
--Neues sample einlesen und Zeitverschiebung
when readAndShift =>
input <= "0" & signed(x_in); --auf 13 Bit signed erweitern(immer positiv)
first_delay <= sum_input;
second_delay <= first_delay;
current_state <= firstMulti;
--Filteroperationen sequentiell durchfuehren
when firstMulti =>
scaled_input <= input * gain; --1.12 * 2.14 = 3.26
current_state <= secondMulti;
when secondMulti =>
a1_mult <= first_delay * pole_a1; --3.26 * 2.14 = 5.40
current_state <= thirdMulti;
when thirdMulti =>
a2_mult <= second_delay * pole_a2; --3.26 * 2.14 = 5.40
current_state <= fourthMulti;
when fourthMulti =>
b1_mult <= first_delay * zero_b1; --3.26 * 2.14 = 5.40
current_state <= fifthMulti;
when fifthMulti =>
b2_mult <= second_delay * zero_b2; --3.26 * 2.14 = 5.40
current_state <= firstSums;
when firstSums =>
-- sum_a1_a2 <= a1_mult(39 downto 11) + a2_mult(39 downto 11); --5.40 to 3.26 CHECKEN!!
-- sum_a1_a2 <= a1_mult(42 downto 14) + a2_mult(42 downto 14); --5.40 to 3.26 CHECKEN!!
sum_b1_b2 <= b1_mult + b2_mult; --5.40 + 5.40
current_state <= truncate1;
when truncate1 =>
a1_mult_truncate <= a1_mult(42 downto 14);
current_state <= truncate2;
when truncate2 =>
a2_mult_truncate <= a2_mult(42 downto 14);
current_state <= secondSums;
when secondSums =>
sum_a1_a2 <= a1_mult_truncate + a2_mult_truncate;
current_state <= thirdSums;
when thirdSums =>
sum_input <= sum_a1_a2 + scaled_input;
current_state <= sixthMulti;
when sixthMulti =>
b0_mult <= sum_input * zero_b0;
current_state <= finalSum;
when finalSum =>
sum_output <= b0_mult + sum_b1_b2;
current_state <= shiftToDA;
--Daten an DA uebermitteln
when shiftToDA =>
y <= std_logic_vector(sum_output(40 downto 29)); --5.40 zu 12 Bit
filter_rdy <= '1'; --DA auf Datenempfang vorbereiten
if (error_from_ADC = '1') then
current_state <= startup;
filter_rdy <= '0';
elsif (enable_data = '0' and data_acknowledged = '1') then
current_state <= waitForADC;
filter_rdy <= '0';
end if;
end case;
else
NULL;
end if;
end process statemachine;
end IIR_DF2_arch;
EDIT 2: Created a DF1 Form now. The timing issues seems to have gone away. Still have the attenuation problem though.
library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;
use IEEE.math_real.all;
entity IIR_DF1 is
port
(
x_in : in std_logic_vector(11 downto 0); --Input 12 Bit vom AD Wandler
clk : in std_logic; --Input Clk mit hoher Frequenz
rst : in std_logic; --Reset Active Low
enable_data : in std_logic; --next Sample vom ADC
data_acknowledged : in std_logic; --DA hat Daten erhalten
error_from_ADC : in std_logic; --Errorsignal vom ADC
filter_rdy : out std_logic; --Signalisiere dem DA ready.
y : out std_logic_vector(11 downto 0) --Output 12 Bit an den DA - Wandler
);
end IIR_DF1;
architecture IIR_DF1_arch of IIR_DF1 is
--States fuer sequentielle Berechnungen
type states is (
startup,
waitForADC,
readAndShift,
firstMulti,
secondMulti,
thirdMulti,
fourthMulti,
fifthMulti,
sixthMulti,
truncate1,
truncate2,
firstSums,
secondSums,
thirdSums,
collectData,
shiftToDA
);
signal current_state : states := startup; --Enthaelt den aktuellen Status, initialisiert mit "startup"
--Gain mit Darstellung 2.14
constant gain : signed(15 downto 0) := X"0123";
--Nullstellen mit Darstellung 2.14
constant zero_b0 : signed(15 downto 0) := X"4000";
constant zero_b1 : signed(15 downto 0) := X"0378";
constant zero_b2 : signed(15 downto 0) := X"4000";
--Polstellen mit Darstellung 2.14
constant pole_a1 : signed(15 downto 0) := X"6DA4"; --Vorzeichen muss invertiert werden!!
constant pole_a2 : signed(15 downto 0) := X"D005"; --Vorzeichen muss invertiert werden!!
----------------------------------------
--Zwischenergebnisse--------------------
----------------------------------------
--Input und Output erweitert auf 1.12 signed
signal input : signed(12 downto 0) := (others => '0');
--Gain
signal scaled_input : signed(28 downto 0) := (others => '0'); --Input nach Gain 3.26
--Summen
signal sum_output : signed(44 downto 0) := (others => '0'); --Summe vor Output 5.40
signal sum_b1_a1 : signed(44 downto 0) := (others => '0'); --Summe nach Multiplikation mit den Polstellen a1 und a2 5.40
signal sum_b2_a2 : signed(44 downto 0) := (others => '0'); --Summe nach Multiplikation mit den Nullstellen b1 und b2 5.40
--Multiplikationen (a0 immer = 1, daher keine Multiplikation)
signal b0_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b0 5.40
signal b1_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b1 5.40
signal b2_mult : signed(44 downto 0) := (others => '0'); --Multiplikation mit b2 5.40
signal a1_mult : signed(60 downto 0) := (others => '0'); --Multiplikation mit a1 7.54
signal a2_mult : signed(60 downto 0) := (others => '0'); --Multiplikation mit a2 7.54
--Delays
signal delay_input_1 : signed(28 downto 0) := (others => '0'); --erster Delay Input 3.26
signal delay_input_2 : signed(28 downto 0) := (others => '0'); --zweiter Delay Input 3.26
signal delay_output_1 : signed(44 downto 0) := (others => '0'); --erster Delay Output 5.40
signal delay_output_2 : signed(44 downto 0) := (others => '0'); --zweiter Delay Output 5.40
--Zwischenstufe Runden
signal a1_mult_truncate : signed(44 downto 0) := (others => '0'); --5.40
signal a2_mult_truncate : signed(44 downto 0) := (others => '0'); --5.40
begin
--Durchfuehren der Operationen abhaengig vom State
statemachine : process(clk, rst)
begin
if (rst = '0') then
current_state <= startup;
--Signalaenderung bei steigender Flanke des 125MHz Clocks
elsif (rising_edge(clk)) then
case (current_state) is
--Alles zuruecksetzen
when startup =>
if (error_from_ADC = '0') then
current_state <= waitForADC;
end if;
--Auf naechstes Sample warten
when waitForADC =>
if (enable_data = '1') then
current_state <= readAndShift;
end if;
--Neues sample einlesen und Zeitverschiebung
when readAndShift =>
input <= "0" & signed(x_in); --auf 13 Bit signed erweitern(immer positiv)
--Input Delays
delay_input_1 <= scaled_input;
delay_input_2 <= delay_input_1;
--Output Delays
delay_output_1 <= sum_output;
delay_output_2 <= delay_output_1;
current_state <= firstMulti;
--Filteroperationen sequentiell durchfuehren
when firstMulti =>
scaled_input <= input * gain; --1.12 * 2.14 = 3.26
current_state <= secondMulti;
when secondMulti =>
b0_mult <= scaled_input * zero_b0; --3.26 * 2.14 = 5.40
current_state <= thirdMulti;
when thirdMulti =>
b1_mult <= delay_input_1 * zero_b1; --3.26 * 2.14 = 5.40
current_state <= fourthMulti;
when fourthMulti =>
b2_mult <= delay_input_2 * zero_b2; --3.26 * 2.14 = 5.40
current_state <= fifthMulti;
when fifthMulti =>
a1_mult <= delay_output_1 * pole_a1; --4.50 * 2.14 = 7.54
current_state <= sixthMulti;
when sixthMulti =>
a2_mult <= delay_output_2 * pole_a2; --4.50 * 2.14 = 7.54
current_state <= truncate1;
when truncate1 =>
a1_mult_truncate <= a1_mult(58 downto 14); --7.54 zu 5.40
current_state <= truncate2;
when truncate2 =>
a2_mult_truncate <= a2_mult(58 downto 14); --7.54 zu 5.40
current_state <= firstSums;
when firstSums =>
sum_b2_a2 <= b2_mult + a2_mult_truncate; --5.40 + 5.40
current_state <= secondSums;
when secondSums =>
sum_b1_a1 <= b1_mult + a1_mult_truncate + sum_b2_a2; --5.40 + 5.40 + 5.40
current_state <= thirdSums;
when thirdSums =>
sum_output <= sum_b1_a1 + scaled_input; --5.40 + 5.40
current_state <= collectData;
when collectData =>
y <= std_logic_vector(sum_output(40 downto 29)); --5.40 zu 12 Bit
current_state <= shiftToDA;
--Daten an DA uebermitteln
when shiftToDA =>
-- y <= std_logic_vector(sum_output(40 downto 29)); --5.40 zu 12 Bit
filter_rdy <= '1'; --DA auf Datenempfang vorbereiten
if (error_from_ADC = '1') then
current_state <= startup;
filter_rdy <= '0';
elsif (enable_data = '0' and data_acknowledged = '1') then
current_state <= waitForADC;
filter_rdy <= '0';
end if;
end case;
else
NULL;
end if;
end process statemachine;
end IIR_DF1_arch;
EDIT 3:
I've got another updated. I could fix the amplification problem with truncating the bits before every multiplication. So the DF1 form filter 2nd order works fine now. My next step was to connect three DF1 2nd order filters to a 6th order IIR which should work as a bandpass. The first filter stage receives the input from the ADC. When all computations are done here, the result is given to the 2nd stage, then the last stage. From here, the DAC receives the output. But my output seems to be maybe unstable? I will add a picture where my input stays at 1,65V constantly. The output seems to try to stay at 1,65V aswell, but never manages to hold this value.