Codebeispiel für FIR / IIR-Filter in VHDL?

11

Ich versuche, mit DSP in meinem Spartan-3-Board zu beginnen. Ich habe eine AC97-Karte mit einem Chip von einem alten Motherboard hergestellt und bisher ADC ausgeführt, die Samples mit einer Zahl <1 multipliziert (Lautstärke verringern) und dann DAC.

Jetzt möchte ich einige grundlegende DSP-Aufgaben ausführen, z. B. einen Tiefpassfilter, einen Hochpass usw. Aber ich bin wirklich verwirrt über die numerische Darstellung (Ganzzahlen? Festpunkt? Q0.15? Überlauf oder Sättigung?).

Ich möchte nur einen Beispielcode eines tatsächlichen einfachen Filters, um mich anzufangen. Keine hohe Effizienz, schnell oder so etwas. Nur der theoretische Filter, der in VHDL implementiert ist.

Ich habe gesucht, aber ich finde nur theoretische Formeln - ich verstehe, was ich nicht verstehe, ist, wie man die signierten 16-Bit-48-kHz-Audio-Samples verarbeitet, die ich vom ADC erhalte. Ich habe diese Bibliotheken verwendet: http://www.vhdl.org/fphdl/ . Wenn ich meine Samples mit 0,5, 0,25 usw. multipliziere, kann ich den Unterschied hören. Aber ein größerer Filter gibt mir nur Rauschen.

Vielen Dank.

hjf
quelle
2
Ich bin zwar dafür, alles, was Sie zur Verfügung haben, zum Lernen zu verwenden, möchte jedoch darauf hinweisen, dass das Ausführen von Audiofiltern in einem FPGA keine sehr effiziente oder kostengünstige Möglichkeit ist, dies zu tun. Wenn Sie also ein echtes Projekt durchführen, würde ich empfehlen, stattdessen einen kostengünstigen DSP zu verwenden. Ausnahmen: Wenn Sie eine gottlose Anzahl von Audiokanälen gleichzeitig machen oder FIRs mit einer absurden Anzahl von Taps machen.

Antworten:

8

Es hört sich so an, als müssten Sie zuerst die DSP-Aspekte herausfinden und dann eine Implementierung in FPGA vornehmen.

  • Sortieren Sie den DSP in C, Matlab, Excel oder anderswo
  • Versuchen Sie zu überlegen, wie Sie das, was Sie daraus gelernt haben, in das FPGA-Land übertragen
  • Stellen Sie fest, dass Sie einige Annahmen über die Implementierung getroffen haben, die nicht gut funktioniert (wie zum Beispiel die Verwendung von Gleitkommazahlen).
  • Gehen Sie zurück und aktualisieren Sie Ihre Offline-DSP-Inhalte, um dies zu berücksichtigen.
  • Iterate n mal :)

In Bezug auf Datentypen können Sie ganzzahlige Zahlen verwenden.

Hier ist ein Beispielcode, der Sie zum Laufen bringt. Beachten Sie, dass viele reale Probleme fehlen (z. B. Zurücksetzen, Überlaufmanagement) - aber hoffentlich lehrreich:

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;
Martin Thompson
quelle
Danke für deine Antwort. Das habe ich mehr oder weniger getan, aber ich habe einige Probleme mit der Darstellung von Zahlen. Mein ADC gibt mir Werte zwischen -32k und + 32k (signiertes 16-Bit). Ich habe auch das Problem der Filterkonstante - wie stelle ich das dar? Und das Ergebnis der Multiplikation zwischen Probe und Konstante? Das verwirrt mich am meisten.
hjf
@hjf - es sind alles nur ganze Zahlen. Solange alles innerhalb von 32 Bit bleibt, sind Sie in Ordnung. Wenn Sie mehr Breite benötigen, können Sie UNSIGNED- oder SIGNED-Vektoren verwenden, die so breit sind, wie Sie möchten. Oder verwenden Sie die Fixed_point-Typen von VHDL2008 (siehe hier: vhdl.org/fphdl )
Martin Thompson
5

Der einfachste Tiefpass-FIR-Filter, den Sie ausprobieren können, ist y (n) = x (n) + x (n-1). Sie können dies ganz einfach in VHDL implementieren. Unten finden Sie ein sehr einfaches Blockdiagramm der Hardware, die Sie implementieren möchten.

Blockdiagramm für einen einfachen Tiefpassfilter

Gemäß der Formel benötigen Sie die aktuellen und vorherigen ADC-Beispiele, um die entsprechende Ausgabe zu erhalten. Was Sie tun sollten, ist, die eingehenden ADC-Abtastwerte an der fallenden Flanke des Takts zu speichern und die entsprechenden Berechnungen an der ansteigenden Flanke durchzuführen, um die entsprechende Ausgabe zu erhalten. Da Sie zwei 16-Bit-Werte addieren, erhalten Sie möglicherweise eine 17-Bit-Antwort. Sie sollten die Eingabe in 17-Bit-Registern speichern und einen 17-Bit-Addierer verwenden. Ihre Ausgabe sind jedoch die unteren 16 Bits der Antwort. Code sieht vielleicht so aus, aber ich kann nicht garantieren, dass er vollständig funktioniert, da ich ihn nicht getestet oder gar synthetisiert habe.

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

Wie Sie sehen können, können Sie diese allgemeine Idee verwenden, um kompliziertere Formeln hinzuzufügen, z. B. solche mit Koeffizienten. Kompliziertere Formeln wie IIR-Filter erfordern möglicherweise die Verwendung von Variablen, um die Algorithmuslogik korrekt zu machen. Eine einfache Möglichkeit, Filter mit reellen Zahlen als Koeffizienten zu umgehen, besteht darin, einen Skalierungsfaktor zu finden, damit alle Zahlen so nahe wie möglich an ganzen Zahlen liegen. Ihr Endergebnis muss um denselben Faktor verkleinert werden, um das richtige Ergebnis zu erhalten.

Ich hoffe, dies kann Ihnen von Nutzen sein und Ihnen helfen, den Ball ins Rollen zu bringen.

* Dies wurde so bearbeitet, dass sich das Daten-Latching und das Output-Latching in getrennten Prozessen befinden. Verwenden Sie auch signierte Typen anstelle von std_logic_vector. Ich gehe davon aus, dass Ihr ADC-Eingang ein std_logic_vector-Signal sein wird.

dhsieh2
quelle
2
Es ist sehr unwahrscheinlich, dass Prozesse, die beide Kanten auslösen (wie Sie beschrieben haben), synthetisiert werden
Martin Thompson,
@Martin Ich gehe davon aus, dass Sie viel mehr über FPGAs wissen als ich, aber ich habe eingehende Daten an der fallenden Flanke und die Ausgabe an der ansteigenden Flanke für eine Klassenzuweisung zwischengespeichert, also dachte ich, dass dies funktioniert hätte. Können Sie erklären, warum solche Prozesse nicht funktionieren?
Dhsieh2
3
In einem Simulator funktioniert es einwandfrei. Synthesizer werden jedoch (meiner Erfahrung nach) daran ersticken, da die Flipflops im Gerät nur an einer Flanke takten können .
Martin Thompson
@ dhsieh2 Danke, das ist die Art von Antwort, nach der ich gesucht habe. Eine andere Frage, wie würde ich das machen, wenn ich signierte Zahlen verwenden würde (mein ADC gibt mir Werte zwischen -32k und + 32k).
hjf
4
@Martin Ich takte die ganze Zeit in Xilinx-FPGAs Dinge von beiden Taktflanken ab, kein Problem. Sie können einfach nicht den gleichen FF von beiden Kanten abtakten. Wenn Sie sich die Ausgabe des Timing-Analysators ansehen, wird deutlich, dass Sie entgegengesetzte Kanten ausführen, und das Timing-Budget wird entsprechend angepasst.
5

Ein weiteres einfaches Code-Snippet (nur der Mut). Hinweis: Ich habe die VHDL nicht direkt geschrieben, sondern MyHDL zum Generieren der VHDL verwendet.

-- 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;

synthetisierte Schaltung

Dies ist eine direkte Implementierung. Es werden Multiplikatoren benötigt. Die Synthese dieser Schaltung, die auf einen Altera Cyclone III abzielte, verwendete keine expliziten Multiplikatoren, sondern erforderte 350 Logikelemente.

Dies ist ein kleiner FIR-Filter und hat die folgende Antwort (nicht so gut), sollte aber als Beispiel nützlich sein.

Filterantwort

Außerdem habe ich hier und hier ein paar Beispiele, die nützlich sein könnten.

Außerdem scheint Ihre Frage zu lauten: "Was ist eine angemessene Festpunktdarstellung?" Bei der Implementierung von DSP-Funktionen wird häufig die Festpunktdarstellung verwendet, da dies die Analyse der Filter vereinfacht. Wie bereits erwähnt, ist der Festpunkt nur eine ganzzahlige Arthimetik. Die eigentliche Implementierung arbeitet einfach mit ganzen Zahlen, aber unsere vorgefertigte Darstellung ist gebrochen.
Probleme treten normalerweise beim Konvertieren von Implementierungs-Ganzzahlen (Festkomma) in Design-Gleitkomma auf.

Ich weiß nicht, wie gut die VHDL-Festkomma- und Gleitkommatypen unterstützt werden. Sie werden in der Simulation gut funktionieren, aber ich weiß nicht, ob sie mit den meisten Synthesewerkzeugen synthetisieren werden. Ich habe dafür eine separate Frage erstellt .

Christopher Felton
quelle
3

OpenCores verfügt über eine Reihe von DSP-Beispielen, IIR und FIR, einschließlich BiQuad. Sie müssen sich registrieren, um die Dateien herunterladen zu können.

edit
Ich verstehe Kortuks Kommentar zu toten Links, und wenn der Link zu OpenCores stirbt, wird die Antwort nutzlos. Ich bin ziemlich zuversichtlich, dass dies nicht passieren wird. Mein Link ist generisch und stirbt nur, wenn die gesamte OpenCores-Domain verschwindet.
Ich habe versucht, nach Beispielen zu suchen, die ich für diese Antwort verwenden könnte, aber sie sind alle zu lang, um hier dargestellt zu werden. Also werde ich mich an meinen Rat halten, mich selbst für die Site zu registrieren (ich musste nach New York ziehen, weil meine Heimatstadt nicht akzeptiert wurde) und mir den dort präsentierten Code ansehen.

stevenvh
quelle
Wie bei allen Dingen brechen Links. Wir haben zuvor diskutiert, dass ein Link alleine keine Antwort gibt. Können Sie etwas von dem, was da ist, überbringen und eine fleischige Antwort geben, die diesen Link als Referenz hat, um mehr zu erfahren?
Kortuk
@Kortuk - Ich wollte das gestern machen. Ich habe mich gestern bei opencores registriert, um einige Details zu erfahren, aber sie brauchen ein paar Tage, um zu
überlegen
Ich war froh, das zu hören und fragte mich ehrlich, ob dir etwas in die Quere gekommen war. Freuen Sie sich darauf, mehr darüber zu hören.
Kortuk
1

Ich habe versucht, Skripte für die authomatische Implementierung von IIR-Filtern zu implementieren, in denen Sie festlegen können, ob das Design so schnell wie möglich (damit jede Multiplikation mit einem dedizierten Multiplikator durchgeführt wird) oder so klein wie möglich (damit jeder Multiplikator wiederverwendet wird) sein soll.

Die Quellen wurden auf alt.sources als "Verhaltensbezogene, aber synthetisierbare Implementierung von IIR-Filtern in VHDL" veröffentlicht (Sie finden sie auch im Google-Archiv: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec) dmode = source )

Beiträge zu alt.sources sind im "shar" -Format, daher müssen Sie die Nachricht als Text speichern und die Freigabe (mit dem Dienstprogramm "unshar") aufheben, um Quellen abzurufen.

WZab
quelle
0

Wie wäre es damit? https://github.com/MauererM/VIIRF

Es implementiert einen Biquad-basierten IIR-Filter (SOS, Abschnitte zweiter Ordnung), der sich um die Festkomma-Implementierung kümmert. Es enthält auch Python-Skripte zum Entwerfen und Überprüfen des Filters. Es werden keine herstellerspezifischen FPGA-Konstrukte verwendet, und Sie können den Kompromiss zwischen Hochgeschwindigkeits- und Niedrigbereichsnutzung wählen.

KeyNuts
quelle