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

Ich versuche, mit DSP in meinem Spartan-3-Board zu beginnen. Ich habe ein AC97-Board mit einem Chip von einem alten Motherboard gemacht, und bisher habe ich es geschafft, ADC zu machen, die Samples für eine Zahl <1 (Lautstärke verringern) und dann DAC zu multiplizieren.

Jetzt würde ich gerne ein paar grundlegende DSP-Sachen machen, wie einen Tiefpassfilter, einen Hochpass usw. Aber ich bin wirklich verwirrt über die numerische Darstellung (Ganzzahlen? Festkomma? Q0.15? Überlauf oder Sättigung?).

Ich möchte nur einen Beispielcode eines tatsächlichen einfachen Filters, um mir den Einstieg zu erleichtern. Keine hocheffiziente, schnelle oder ähnliches. Nur der in VHDL implementierte theoretische Filter.

Ich habe gesucht, aber ich finde nur theoretische Formeln - ich verstehe, was ich nicht verstehe, ist, wie die signierten 16-Bit-48-kHz-Audio-Samples verarbeitet werden, die ich vom ADC bekomme. 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.

Während ich dafür bin, alles zu verwenden, was Sie zur Hand haben, um Dinge zu lernen, möchte ich darauf hinweisen, dass das Erstellen von Audiofiltern in einem FPGA kein sehr effizienter oder kostengünstiger Weg ist, dies zu tun. Wenn Sie also ein echtes Projekt durchführen, würde ich stattdessen die Verwendung eines kostengünstigen DSP empfehlen. Ausnahmen: Wenn Sie eine unglaubliche Anzahl von Audiokanälen gleichzeitig ausführen oder FIRs mit einer absurden Anzahl von Taps ausführen.

Antworten (6)

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 aus
  • Überlegen Sie, wie Sie das, was Sie daraus gelernt haben, in das FPGA-Land übertragen können
  • Stellen Sie fest, dass Sie eine Annahme über die Implementierung getroffen haben, die nicht gut funktioniert (z. B. die Verwendung von Gleitkommazahlen).
  • Gehen Sie zurück und aktualisieren Sie Ihre Offline-DSP-Sachen, um dies zu berücksichtigen.
  • Iteriere n mal :)

In Bezug auf Datentypen können Sie problemlos ganze Zahlen verwenden.

Hier ist ein Beispielcode, um Ihnen den Einstieg zu erleichtern. Beachten Sie, dass viele reale Probleme fehlen (z. B. Zurücksetzen, Überlaufverwaltung) - aber hoffentlich aufschlussreich sind:

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;
Danke für deine Antwort. Das ist mehr oder weniger das, was ich getan habe, aber ich habe einige Probleme mit der Zahlendarstellung. Mein ADC gibt mir Werte im Bereich von -32k bis +32k (signed 16-bit). Ich habe auch das Problem der Filterkonstante - wie stelle ich das dar? Und das Ergebnis der Multiplikation zwischen der Probe und der Konstante? Das verwirrt mich am meisten.
@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 so breit verwenden, wie Sie möchten. Oder verwenden Sie die Fixed_point-Typen von VHDL2008 (siehe hier: vhdl.org/fphdl )

Ein weiteres einfaches Code-Snippet (nur der Mut). Hinweis: Ich habe die VHDL nicht direkt geschrieben, ich habe MyHDL verwendet, um die VHDL zu generieren.

-- 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 für einen Altera Cyclone III gedacht war, verwendete keine expliziten Multiplikatoren, sondern erforderte 350 Logikelemente.

Dies ist ein kleiner FIR-Filter und hat die folgende Reaktion (nicht so großartig), 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 fragen: "Was ist eine angemessene Festkommadarstellung?" Häufig wird bei der Implementierung von DSP-Funktionen die Festkommadarstellung verwendet, weil sie die Analyse der Filter vereinfacht. Wie bereits erwähnt, ist Festkomma nur eine ganzzahlige Arthimetik. Die eigentliche Implementierung arbeitet einfach mit ganzen Zahlen, aber unsere angenommene Darstellung ist gebrochen.
Probleme treten normalerweise beim Konvertieren von Implementierungs-Integer (Festkomma) zu/von 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 synthetisiert werden können. Dazu habe ich eine separate Frage erstellt .

Danke für deine Antwort. Haben Sie Ressourcen, wo ich DSP in VHDL und Codegenerierung lernen kann (Tutorials oder Bücher)?

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 sehen 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-Samples, um die entsprechende Ausgabe zu erhalten. Was Sie tun sollten, ist, die eingehenden ADC-Samples an der fallenden Flanke des Takts zu speichern und die entsprechenden Berechnungen an der steigenden Flanke durchzuführen, um die entsprechende Ausgabe zu erhalten. Da Sie zwei 16-Bit-Werte addieren, ist es möglich, dass Sie am Ende eine 17-Bit-Antwort erhalten. 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 könnte etwa so aussehen, aber ich kann nicht garantieren, dass er vollständig funktioniert, da ich ihn nicht getestet, geschweige denn 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 erhalten. Schließlich besteht eine einfache Möglichkeit, Filter mit reellen Zahlen als Koeffizienten zu umgehen, darin, einen Skalierungsfaktor zu finden, damit alle Zahlen so nah wie möglich an ganzen Zahlen liegen. Ihr Endergebnis muss um denselben Faktor herunterskaliert 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 die Datenspeicherung und die Ausgangsspeicherung in separaten Prozessen erfolgen. 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.

Es ist sehr unwahrscheinlich, dass Prozesse, die beide Kanten auslösen (wie Sie beschrieben haben), synthetisiert werden
@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 steigenden Flanke für eine Klassenzuweisung zwischengespeichert, also dachte ich, das hätte funktioniert. Können Sie erklären, warum solche Prozesse nicht funktionieren?
In einem Simulator wird es gut funktionieren. Synthesizer werden jedoch daran ersticken (meiner Erfahrung nach), da die Flipflops im Gerät nur an einer Kante takten können.
@ dhsieh2 Danke, das ist die Art von Antwort, nach der ich gesucht habe. Eine andere Frage, wie würde ich es tun, wenn ich vorzeichenbehaftete Zahlen verwenden würde (mein ADC gibt mir Werte im Bereich von -32k bis +32k).
@hjf das ist ein guter Punkt. Ich hätte "signed" anstelle von "std_logic_vector" verwenden sollen. Das sollte in der Bibliothek IEEE.numeric_std.all enthalten sein. Andernfalls müssen Sie diese so, wie ich es jetzt habe, in vorzeichenbehaftete Typen konvertieren (und dann zurück in std_logic_vector-Typen), bevor Sie die Addition durchführen können. Ich werde diese Änderungen in meiner Antwort vornehmen.
@Martin Ich takte die Dinge in Xilinx FPGAs die ganze Zeit von beiden Taktflanken ab, kein Problem. Sie können einfach nicht das gleiche FF von beiden Kanten abtakten. Wenn Sie sich die Ausgabe des Timing-Analyzers ansehen, wird deutlich, dass Sie entgegengesetzte Flanken ausführen, und passt das Timing-Budget entsprechend an.
@David - Ich stimme zu, aber zum Zeitpunkt meines Kommentars wurde der Code mit zwei Kanten in einem Prozess geschrieben. Wie auch immer, diese Debatte ist alles ein bisschen OT zum Thema FIR-Implementierungen :)
@MartinThompson, ich würde es tangential und sehr interessant für andere Benutzer beim Lesen nennen, obwohl ich wirklich nur für mich selbst sprechen kann.

OpenCores hat eine Reihe von DSP-Beispielen, IIR und FIR, einschließlich BiQuad. Sie müssen sich registrieren, um die Dateien herunterzuladen.

Bearbeiten
Ich verstehe Kortuks Kommentar zu toten Links, und in der Tat, wenn der Link zu OpenCores stirbt, wird die Antwort nutzlos. Ich bin ziemlich zuversichtlich, dass dies nicht passieren wird; mein Link ist ein generischer Link, und er wird nur sterben, wenn die komplette OpenCores-Domäne verschwinden würde.
Ich habe versucht, nach einigen Beispielen zu suchen, die ich für diese Antwort verwenden könnte, aber sie sind alle zu lang, um hier dargestellt zu werden. Also bleibe ich bei meinem Rat, mich selbst für die Seite zu registrieren (ich musste nach New York umziehen, weil mein Heimatort nicht akzeptiert wurde) und mir den dort präsentierten Code ansehen.

Wie bei allen Dingen brechen Links. Wir haben bereits besprochen, dass ein Link allein keine Antwort liefert. Können Sie etwas von dem, was da ist, mitbringen und eine fleischige Antwort geben, die diesen Link als Referenz enthält, um mehr zu erfahren?
@Kortuk - das wollte ich gestern machen. Ich habe mich gestern bei Opencores registriert, um ein paar Details zu bekommen, aber sie brauchen ein paar Tage, um zu überlegen, ob sie mich haben werden
Freut mich zu hören, ich habe mich ehrlich gesagt gefragt, ob Ihnen etwas in die Quere gekommen ist. Freuen Sie sich darauf, mehr darüber zu hören.

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

Die Quellen wurden auf alt.sources als „Behavioral but synthesizable implementierung of IIRfilters in VHDL“ veröffentlicht (auch im Google-Archiv zu finden: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ?dmode=Quelle )

Posts an alt.sources sind im „shar“-Format, also müssen Sie die Nachricht als Text speichern und die Freigabe aufheben (mit dem „unshar“-Dienstprogramm), um Quellen zu erhalten.

Die Google-Gruppen liefern Shar-Dateien nicht korrekt. Eine Kopie des referenzierten Beitrags kann unter ftp.funet.fi/pub/archive/alt.sources/2600.gz gefunden werden . Der Code selbst ist unter github.com/wzab/wzab-hdl-library/tree/master/… verfügbar.

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

Es implementiert einen auf Biquad (SOS, Abschnitte zweiter Ordnung) basierenden IIR-Filter, der sich um die Festkommaimplementierung kümmert. Es enthält auch Python-Skripte für das Design und die Überprüfung des Filters. Es verwendet keine herstellerspezifischen FPGA-Konstrukte, und Sie können den Kompromiss zwischen hoher Geschwindigkeit und geringer Flächennutzung wählen.