Przykład kodu dla filtrów FIR / IIR w VHDL?


11

Próbuję zacząć korzystać z DSP na mojej płycie Spartan-3. Zrobiłem płytę AC97 z układem ze starej płyty głównej i do tej pory udało mi się zrobić ADC, pomnożyć próbki dla liczby <1 (zmniejszyć głośność), a następnie DAC.

Teraz chciałbym zrobić kilka podstawowych rzeczy DSP, takich jak filtr dolnoprzepustowy, górnoprzepustowy itp. Ale jestem naprawdę zdezorientowany co do reprezentacji numerycznej (liczby całkowite? Punkt stały? Q0.15? Przepełnienie lub nasycenie?).

Chcę tylko przykładowy kod rzeczywistego prostego filtra, aby zacząć. Brak wysokiej wydajności, szybkości lub czegoś podobnego. Tylko filtr teoretyczny zaimplementowany w VHDL.

Szukałem, ale właśnie znalazłem wzory teoretyczne - rozumiem, ale nie rozumiem, jak przetwarzać podpisane 16-bitowe próbki audio 48 KHz, które otrzymuję z ADC. Korzystałem z tych bibliotek: http://www.vhdl.org/fphdl/ . Jeśli pomnożę próbki przez 0,5, 0,25 itd., Słyszę różnicę. Ale większy filtr daje mi tylko szum.

Dzięki.


2
Chociaż jestem za używaniem wszystkiego, co masz pod ręką, do nauki, chciałbym zauważyć, że robienie filtrów audio w FPGA nie jest zbyt wydajnym ani opłacalnym sposobem na zrobienie tego. Tak więc, jeśli wykonujesz prawdziwy projekt, zaleciłbym zamiast tego użycie taniego DSP. Wyjątki: gdy robisz jednocześnie bezbożną liczbę kanałów audio lub robisz FIR z absurdalną liczbą uderzeń.

Odpowiedzi:


8

Wygląda na to, że musisz najpierw zrozumieć aspekty DSP, a następnie wprowadzić implementację w FPGA.

  • Posortuj DSP w C, Matlab, Excel lub gdziekolwiek indziej
  • Spróbuj pomyśleć, jak przeniesiesz to, czego się nauczyłeś, na ziemię FPGA
  • Odkryj, że masz pewne założenia dotyczące implementacji, która nie działa dobrze (na przykład użycie zmiennoprzecinkowe)
  • Wróć i zaktualizuj swoje DSP offline, aby wziąć to pod uwagę.
  • Iteruj n razy :)

Jeśli chodzi o typy danych, możesz dobrze używać liczb całkowitych.

oto przykładowy kod na początek. Pamiętaj, że brakuje wielu problemów w świecie rzeczywistym (na przykład resetowania, zarządzania przepełnieniem) - ale mam nadzieję, że jest pouczający:

library ieee;
use ieee.std_logic_1164.all;
entity simple_fir is
    generic (taps : integer_vector); 
    port (
        clk      : in  std_logic;
        sample   : in  integer;
        filtered : out integer := 0);
end entity simple_fir;
----------------------------------------------------------------------------------------------------------------------------------
architecture a1 of simple_fir is
begin  -- architecture a1
    process (clk) is
        variable delay_line : integer_vector(0 to taps'length-1) := (others => 0);
        variable sum : integer;
    begin  -- process
        if rising_edge(clk) then  -- rising clock edge
            delay_line := sample & delay_line(0 to taps'length-2);
            sum := 0;
            for i in 0 to taps'length-1 loop
                sum := sum + delay_line(i)*taps(taps'high-i);
            end loop;
            filtered <= sum;
        end if;
    end process;
end architecture a1;
----------------------------------------------------------------------------------------------------------------------------------
-- testbench
----------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity tb_simple_fir is
end entity tb_simple_fir;
architecture test of tb_simple_fir is
    -- component generics
    constant lp_taps : integer_vector := ( 1, 1, 1, 1, 1);
    constant hp_taps : integer_vector := (-1, 0, 1);

    constant samples : integer_vector := (0,0,0,0,1,1,1,1,1);

    signal sample   : integer;
    signal filtered : integer;
    signal Clk : std_logic := '1';
    signal finished : std_logic;
begin  -- architecture test
    DUT: entity work.simple_fir
        generic map (taps => lp_taps)  -- try other taps in here
        port map (
            clk      => clk,
            sample   => sample,
            filtered => filtered);

    -- waveform generation
    WaveGen_Proc: process
    begin
        finished <= '0';
        for i in samples'range loop
            sample <= samples(i);
            wait until rising_edge(clk);
        end loop;
        -- allow pipeline to empty - input will stay constant
        for i in 0 to 5 loop
            wait until rising_edge(clk);
        end loop;
        finished <= '1';
        report (time'image(now) & " Finished");
        wait;
    end process WaveGen_Proc;

    -- clock generation
    Clk <= not Clk after 10 ns when finished /= '1' else '0';
end architecture test;

Dziękuję za odpowiedź. Tak mniej więcej to zrobiłem, ale mam pewne problemy z reprezentacją liczb. Mój ADC daje mi wartości od -32k do + 32k (podpisany 16-bit). Mam również problem stałej filtru - jak to przedstawić? A wynik pomnożenia między próbką a stałą? To mnie najbardziej dezorientuje.
hjf

@ hjf - to tylko liczby całkowite. Dopóki wszystko mieści się w granicach 32 bitów, wszystko jest w porządku. JEŻELI potrzebujesz większej szerokości, możesz użyć wektorów NIE PODPISANYCH lub PODPISANYCH tak szerokich, jak chcesz. Lub użyj typów fixed_point z VHDL2008 (patrz tutaj: vhdl.org/fphdl )
Martin Thompson

5

Najprostszy filtr dolnoprzepustowy FIR, jaki możesz wypróbować, to y (n) = x (n) + x (n-1). Możesz to dość łatwo zaimplementować w VHDL. Poniżej znajduje się bardzo prosty schemat blokowy sprzętu, który chcesz wdrożyć.

Schemat blokowy dla prostego filtra dolnoprzepustowego

Zgodnie ze wzorem potrzebne są bieżące i poprzednie próbki ADC, aby uzyskać odpowiednią moc wyjściową. Co należy zrobić, to zatrzasnąć przychodzące próbki ADC na zboczu opadającym zegara i wykonać odpowiednie obliczenia na zboczu narastającym, aby uzyskać odpowiednią moc wyjściową. Ponieważ dodajesz dwie wartości 16-bitowe razem, możliwe, że otrzymasz 17-bitową odpowiedź. Powinieneś zapisać dane wejściowe w 17-bitowych rejestrach i użyć 17-bitowego sumatora. Twój wynik będzie jednak niższymi 16 bitami odpowiedzi. Kod może wyglądać mniej więcej tak, ale nie mogę zagwarantować, że będzie działał całkowicie, ponieważ go nie testowałem, nie mówiąc już o jego syntezie.

IEEE.numeric_std.all;
...
    signal x_prev, x_curr, y_n: signed(16 downto 0);
    signal filter_out: std_logic_vector(15 downto 0);
...
process (clk) is
begin
    if falling_edge(clk) then
        --Latch Data
        x_prev <= x_curr;
        x_curr <= signed('0' & ADC_output); --since ADC is 16 bits
    end if;
end process;

process (clk) is
begin
    if rising_edge(clk) then
        --Calculate y(n)
        y_n <= x_curr + x_prev;
    end if;
end process;

filter_out <= std_logic_vector(y_n(15 downto 0));  --only use the lower 16 bits of answer

Jak widać, można użyć tego ogólnego pomysłu, aby dodać bardziej skomplikowane formuły, na przykład o współczynnikach. Bardziej skomplikowane formuły, takie jak filtry IIR, mogą wymagać użycia zmiennych, aby uzyskać poprawną logikę algorytmu. Wreszcie, łatwym sposobem na obejście filtrów, które mają liczby rzeczywiste jako współczynniki, jest znalezienie współczynnika skali, aby wszystkie liczby były jak najbardziej zbliżone do liczb całkowitych. Ostateczny wynik będzie musiał zostać zmniejszony o ten sam współczynnik, aby uzyskać poprawny wynik.

Mam nadzieję, że to może ci się przydać i pomóc w toczyć piłkę.

* Zostało to zmodyfikowane, aby blokowanie danych i blokowanie danych wyjściowych odbywało się w osobnych procesach. Również używając podpisanych typów zamiast std_logic_vector. Zakładam, że twoje wejście ADC będzie sygnałem std_logic_vector.


2
Procesy wyzwalające obie krawędzie (jak opisano) są bardzo mało prawdopodobne, aby je zsyntetyzować
Martin Thompson

@Martin Zakładam, że wiesz o wiele więcej na temat układów FPGA niż ja, ale zablokowałem przychodzące dane na zboczu opadającym i zablokowałem wyjście na zboczu narastającym dla zadania klasy, więc pomyślałem, że to zadziała. Czy możesz wyjaśnić, dlaczego takie procesy nie działają?
dhsieh2

3
Będzie działać dobrze w symulatorze. Syntezatory będą się na nim dławić (z mojego doświadczenia), ponieważ klapki w urządzeniu mogą taktować tylko jedną krawędź.
Martin Thompson

@ dhsieh2 Dzięki, tego rodzaju odpowiedzi szukałem. Kolejne pytanie, jak miałbym to zrobić, gdybym używał Podpisanych liczb (mój ADC podaje mi wartości w zakresie od -32k do +32k).
hjf

4
@Martin I tak cały czas mierzy z obu krawędzi zegara w FPGA Xilinx, nie ma problemu. Po prostu nie można ustawić tego samego FF na obu krawędziach. Kiedy spojrzysz na dane wyjściowe analizatora taktowania, w rzeczywistości wyraźnie widać, że robisz przeciwne krawędzie i odpowiednio dostosowuje budżet czasowy.

5

Kolejny prosty fragment kodu (tylko wnętrzności). Uwaga: Nie napisałem VHDL bezpośrednio, użyłem MyHDL do wygenerowania VHDL.

-- VHDL code snip
architecture MyHDL of sflt is

type t_array_taps is array(0 to 6-1) of signed (15 downto 0);
signal taps: t_array_taps;

begin

SFLT_RTL_FILTER: process (clk) is
    variable sum: integer;
begin
    if rising_edge(clk) then
        sum := to_integer(x * 5580);
        sum := to_integer(sum + (taps(0) * 5750));
        sum := to_integer(sum + (taps(1) * 6936));
        sum := to_integer(sum + (taps(2) * 6936));
        sum := to_integer(sum + (taps(3) * 5750));
        sum := to_integer(sum + (taps(4) * 5580));
        taps(0) <= x;
        for ii in 1 to 5-1 loop
            taps(ii) <= taps((ii - 1));
        end loop;
        y <= to_signed(sum, 16);
    end if;
end process SFLT_RTL_FILTER;

end architecture MyHDL;

obwód syntezowany

Jest to bezpośrednie wdrożenie. Będzie to wymagało mnożników. Synteza tego obwodu, ukierunkowana na Altera Cyclone III, nie wykorzystywała żadnych wyraźnych mnożników, ale wymagała 350 elementów logicznych.

To jest mały filtr FIR i będzie miał następującą odpowiedź (niezbyt dobrą), ale powinien być przydatny jako przykład.

odpowiedź filtra

Ponadto mam kilka przykładów tu i tutaj , które mogą być przydatne.

Wydaje się również, że pytanie brzmi: „jaka jest odpowiednia reprezentacja punktu stałego?” Często podczas implementacji funkcji DSP używana jest reprezentacja punktu stałego, ponieważ upraszcza to analizę filtrów. Jak wspomniano, punkt stały jest po prostu całkowitym artymetykiem. Rzeczywista implementacja po prostu działa z liczbami całkowitymi, ale nasza wcześniejsza reprezentacja jest ułamkowa.
Problemy zwykle pojawiają się podczas konwersji z liczby całkowitej implementacji (punkt stały) na zmiennoprzecinkowy projekt / z powrotem.

Nie wiem, jak dobrze obsługiwane są typy stałoprzecinkowe i zmiennoprzecinkowe VHDL. Będą działać dobrze w symulacji, ale nie wiem, czy będą syntetyzować za pomocą większości narzędzi do syntezy. Stworzyłem do tego osobne pytanie .


3

OpenCores ma wiele przykładów DSP, IIR i FIR, w tym BiQuad. Musisz się zarejestrować, aby pobrać pliki.

edytuj
Rozumiem komentarz Kortuka na temat martwych linków i jeśli umrze link do OpenCores, odpowiedź stanie się bezużyteczna. Jestem przekonany, że tak się nie stanie; mój link jest ogólny i umrze tylko wtedy, gdy zniknie cała domena OpenCores.
Próbowałem poszukać przykładów, których mógłbym użyć dla tej odpowiedzi, ale są one zbyt długie, aby je tu przedstawić. Będę się więc trzymał mojej rady, aby zarejestrować się na stronie (musiałem przeprowadzić się do Nowego Jorku, ponieważ moje rodzinne miasto nie zostało zaakceptowane) i rzucić okiem na kod tam przedstawiony.


Jak w przypadku wszystkich rzeczy, linki się psują. Przedyskutowaliśmy wcześniej, że sam link nie daje odpowiedzi. Czy możesz przekazać niektóre z nich i udzielić mięsistej odpowiedzi, która zawiera ten link jako odniesienie, aby dowiedzieć się więcej?
Kortuk

@Kortuk - chciałem to zrobić wczoraj. Wczoraj zarejestrowałem się w opencores, aby uzyskać szczegółowe informacje, ale potrzebują kilku dni, aby pomyśleć, czy mnie
dostaną

Cieszę się, że to słyszę, szczerze zastanawiałem się, czy coś ci nie przeszkodziło. Czekamy na więcej informacji na ten temat.
Kortuk

1

Próbowałem zaimplementować skrypty do automatycznej implementacji filtrów IIR, w których można określić, czy projekt powinien być tak szybki, jak to możliwe (aby każde zwielokrotnienie odbywało się za pomocą dedykowanego mnożnika), czy jak najmniejsze (aby każdy mnożnik był ponownie wykorzystywany).

Źródła zostały opublikowane na alt.sources jako „Behawioralna, ale możliwa do syntezy implementacja filtrów IIR w VHDL” (można go również znaleźć w archiwum Google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ? dmode = źródło )

Wpisy do alt.sources są w formacie „shar”, więc musisz zapisać wiadomość jako tekst i oddzielić ją (za pomocą narzędzia „unshar”), aby uzyskać źródła.


0

Co powiesz na to? https://github.com/MauererM/VIIRF

Implementuje filtr IIR oparty na biquad (SOS, sekcje drugiego rzędu), który zajmuje się implementacją punktu stałego. Zawiera również skrypty Pythona do projektowania i weryfikacji filtra. Nie wykorzystuje konstruktorów FPGA specyficznych dla Dostawców i możesz wybrać kompromis między szybkim i niskim obszarem użytkowania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.