Ist es möglich, einen IIR-Filter in einem FPGA zu erstellen, der mit der Abtastfrequenz getaktet ist?

9

Bei dieser Frage geht es um die Implementierung eines IIR-Filters in einem FPGA mit DSP-Slices mit sehr spezifischen Kriterien.

Nehmen wir an, Sie erstellen einen Filter ohne Vorwärtsabgriffe und nur 1 Rückwärtsabgriff mit dieser Gleichung:

y[n]=y[n1]b1+x[n]

(siehe Bild)

Nehmen Sie als Beispiel das DSP48A1-Slice von Xilinx - die meisten harten IP-DSP-Slices sind ähnlich.

Nehmen wir an, Sie haben analoge Daten, die mit 1 Abtastwert pro Takt eingehen. Ich möchte einen IIR-Filter entwerfen, der synchron mit dem Sample-Clock läuft.

Das Problem ist, dass Sie nicht multiplizieren UND zum selben Zyklus hinzufügen können, um das DSP-Slice mit der maximalen Rate auszuführen. Sie müssen ein Pipeline-Register zwischen diesen Komponenten haben.

Wenn Sie also pro Takt 1 neues Sample haben, müssen Sie 1 Ausgang pro Takt erzeugen. Sie benötigen jedoch die vorherigen 2 Takte, bevor Sie eine neue in diesem Design erstellen können.

Die naheliegende Lösung besteht darin, die Daten entweder mit doppelter Taktrate zu verarbeiten oder das Pipeline-Register zu deaktivieren, damit Sie im selben Zyklus multiplizieren und addieren können.

Wenn Sie beispielsweise mit der maximalen Taktrate des DSP-Slice mit vollständiger Pipeline abtasten, ist leider keine dieser Lösungen möglich. Gibt es eine andere Möglichkeit, dies zu bauen?

(Bonuspunkte, wenn Sie einen IIR-Filter entwerfen können, der mit einer beliebigen Anzahl von DSP-Slices mit der Hälfte der Abtastrate arbeitet)

Das Ziel wäre, einen Kompensationsfilter für einen 1-GSPS-ADC in einem Xilinx Artix-FPGA auszuführen. Ihre DSP-Slices können bei voller Pipeline etwas mehr als 500 MHz laufen. Wenn es eine Lösung für 1 Probe pro Uhr gibt, möchte ich versuchen, die Lösung für 2 Proben pro Uhr zu skalieren. Mit einem FIR-Filter ist das alles sehr einfach.

Beispiel für einen IIR-Filter mit Einzelrückkopplung

Marcus10110
quelle
1
Nur zur Klarstellung, es gibt keinen Grund, warum Sie mit der Pipeline-Methode nicht einen Ausgang pro Taktzyklus hätten, oder? Sie versuchen, die Latenz auf einen Taktzyklus anstatt auf zwei zu minimieren, oder? Abhängig von Ihrer Situation können Sie, wenn Sie eine Ganzzahl für b1 verwenden, die Multiplikation in eine riesige Addition mit x [n] konvertieren.
Horta
richtig - da es einen Eingang pro Takt gibt, muss es einen Ausgang pro Takt geben. Latenz ist auch kein Problem. Das DSP-Slice hat nur einen Addierer mit 2 Eingängen, und die Abgriffe sind normalerweise ziemlich große Zahlen, so dass Sie in einem Taktzyklus nicht b1-mal hinzufügen können. Die Hauptgrenze besteht darin, dass der Ausgang in 1 Takt zurückgemeldet werden muss, für die Erzeugung jedoch 2 Takte erforderlich sind.
Marcus10110
1
Ich denke, Sie verstehen immer noch falsch, wie eine Pipeline funktioniert. Eine Pipeline erhöht möglicherweise die Latenz, ermöglicht es Ihnen jedoch, bei jedem Taktzyklus 1 Ausgang für jeden Eingang zu erhalten. Es ist nur so, dass das Ergebnis jetzt 2 Uhren später ist und nicht die ideale 1 Uhr danach. Die Eingabe wäre die folgende Sequenz: x [0], x [1], x [2], x [3], x [4], während die Ausgabe im gleichen Zeitintervall y [-2], y wäre [-1], y [0], y [1], y [2]. Sie verlieren keine Proben. Außerdem befinden Sie sich auf einem FPGA. Wenn Sie also mehr Arbeit erledigen möchten als für die DSP-Pipelines vorgesehen, verwenden Sie die fpga, um die Arbeitslast zu parallelisieren.
Horta
Dieser DSP ist in der Lage, eine verschmolzene Multiplikation in einem Zyklus durchzuführen. Es ist mir jedoch unklar, ob der Ausgang eines DSP-Slice mit Rückmeldung in einem einzigen Zyklus mit seinem eigenen Eingang verbunden werden kann.
Jbarlow
horta - Sie haben Recht mit Pipelining im Allgemeinen, aber das Problem ist, dass die Registerkarte b1 in diesem Fall eine Rückmeldung enthält - was bedeutet, dass eine Stufe in der Pipeline von der Ausgabe des vorherigen Werts abhängt. Wenn immer 2 Takte erforderlich sind, um die nächste Ausgabe der vorherigen Ausgabe zu erzeugen, gibt es keine Möglichkeit, 1 Ausgabe pro Takt zu erzeugen, unabhängig davon, wie viel Latenz Sie hinzugefügt haben. jbarlow - Sie haben Recht, das DSP-Slice verfügt über eine 1-Zyklus-Option. In diesem Fall kann es jedoch nicht schnell genug laufen. Durch Hinzufügen des M-Registers (siehe Datenblatt) können Sie 500 MHz erreichen. Sie können jedoch nicht multiplizieren und in demselben clk addieren.
Marcus10110

Antworten:

3

Ich habe noch nicht mit IIR-Filtern gearbeitet, aber wenn Sie nur die angegebene Gleichung berechnen müssen

y[n] = y[n-1]*b1 + x[n]

Einmal pro CPU-Zyklus können Sie Pipelining verwenden.

In einem Zyklus führen Sie die Multiplikation durch und in einem Zyklus müssen Sie die Summierung für jede Eingangsabtastung durchführen. Das bedeutet, dass Ihr FPGA in der Lage sein muss, die Multiplikation in einem Zyklus durchzuführen, wenn es mit der angegebenen Abtastrate getaktet wird! Dann müssen Sie nur noch die Multiplikation des aktuellen Samples UND die Summierung des Multiplikationsergebnisses des letzten Samples parallel durchführen. Dies führt zu einer konstanten Verarbeitungsverzögerung von 2 Zyklen.

Ok, schauen wir uns die Formel an und entwerfen eine Pipeline:

y[n] = y[n-1]*b1 + x[n]

Ihr Pipeline-Code könnte folgendermaßen aussehen:

output <= last_output_times_b1 + last_input
last_output_times_b1 <= output * b1;
last_input <= input

Beachten Sie, dass alle drei Befehle parallel ausgeführt werden müssen und dass "Ausgabe" in der zweiten Zeile daher die Ausgabe aus dem letzten Taktzyklus verwendet!

Ich habe nicht viel mit Verilog gearbeitet, daher ist die Syntax dieses Codes höchstwahrscheinlich falsch (z. B. fehlende Bitbreite der Eingangs- / Ausgangssignale; Ausführungssyntax für die Multiplikation). Sie sollten jedoch auf die Idee kommen:

module IIRFilter( clk, reset, x, b, y );
  input clk, reset, x, b;
  output y;

  reg y, t, t2;
  wire clk, reset, x, b;

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

endmodule

PS: Vielleicht könnte ein erfahrener Verilog-Programmierer diesen Code bearbeiten und diesen Kommentar und den Kommentar über dem Code anschließend entfernen. Vielen Dank!

PPS: Wenn Ihr Faktor "b1" eine feste Konstante ist, können Sie das Design möglicherweise optimieren, indem Sie einen speziellen Multiplikator implementieren, der nur eine Skalareingabe verwendet und nur "Zeiten b1" berechnet.

Antwort auf: "Leider entspricht dies tatsächlich y [n] = y [n-2] * b1 + x [n]. Dies liegt an der zusätzlichen Pipeline-Stufe." als Kommentar zur alten Version der Antwort

Ja, das war eigentlich richtig für die folgende alte (INCORRECT !!!) Version:

  always @ (posedge clk or posedge reset)
  if (reset) begin
    t <= 0;
  end else begin
    y <= t + x;
    t <= mult(y, b);
  end

Ich habe diesen Fehler jetzt hoffentlich behoben, indem ich auch die Eingabewerte in einem zweiten Register verzögert habe:

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

Um sicherzustellen, dass es diesmal richtig funktioniert, schauen wir uns an, was in den ersten Zyklen passiert. Beachten Sie, dass die ersten beiden Zyklen mehr oder weniger (definierten) Müll produzieren, da keine vorherigen Ausgabewerte (z. B. y [-1] == ??) verfügbar sind. Das Register y wird mit 0 initialisiert, was der Annahme von y [-1] == 0 entspricht.

Erster Zyklus (n = 0):

BEFORE: INPUT (x=x[0], b); REGISTERS (t=0, t2=0, y=0)

y <= t + t2;      == 0
t <= mult(y, b);  == y[-1] * b  = 0
t2 <= x           == x[0]

AFTERWARDS: REGISTERS (t=0, t2=x[0], y=0), OUTPUT: y[0]=0

Zweiter Zyklus (n = 1):

BEFORE: INPUT (x=x[1], b); REGISTERS (t=0, t2=x[0], y=y[0])

y <= t + t2;      ==     0  +  x[0]
t <= mult(y, b);  ==  y[0]  *  b
t2 <= x           ==  x[1]

AFTERWARDS: REGISTERS (t=y[0]*b, t2=x[1], y=x[0]), OUTPUT: y[1]=x[0]

Dritter Zyklus (n = 2):

BEFORE: INPUT (x=x[2], b); REGISTERS (t=y[0]*b, t2=x[1], y=y[1])

y <= t + t2;      ==  y[0]*b +  x[1]
t <= mult(y, b);  ==  y[1]   *  b
t2 <= x           ==  x[2]

AFTERWARDS: REGISTERS (t=y[1]*b, t2=x[2], y=y[0]*b+x[1]), OUTPUT: y[2]=y[0]*b+x[1]

Vierter Zyklus (n = 3):

BEFORE: INPUT (x=x[3], b); REGISTERS (t=y[1]*b, t2=x[2], y=y[2])

y <= t + t2;      ==  y[1]*b +  x[2]
t <= mult(y, b);  ==  y[2]   *  b
t2 <= x           ==  x[3]

AFTERWARDS: REGISTERS (t=y[2]*b, t2=x[3], y=y[1]*b+x[2]), OUTPUT: y[3]=y[1]*b+x[2]

Wir können sehen, dass wir beginnend mit cylce n = 2 die folgende Ausgabe erhalten:

y[2]=y[0]*b+x[1]
y[3]=y[1]*b+x[2]

das ist äquivalent zu

y[n]=y[n-2]*b + x[n-1]
y[n]=y[n-1-l]*b1 + x[n-l],  where l = 1
y[n+l]=y[n-1]*b1 + x[n],  where l = 1

Wie oben erwähnt, führen wir eine zusätzliche Verzögerung von l = 1 Zyklen ein. Das bedeutet, dass Ihre Ausgabe y [n] um die Verzögerung l = 1 verzögert ist. Das heißt, die Ausgabedaten sind äquivalent, werden jedoch um einen "Index" verzögert. Um es klarer zu machen: Die Ausgangsdaten verzögern sich um 2 Zyklen, da ein (normaler) Taktzyklus benötigt wird und 1 zusätzlicher (Verzögerung l = 1) Taktzyklus für die Zwischenstufe hinzugefügt wird.

Hier ist eine Skizze, um grafisch darzustellen, wie die Daten fließen:

Skizze des Datenflusses

PS: Danke, dass Sie sich meinen Code genau angesehen haben. Also habe ich auch etwas gelernt! ;-) Lass mich wissen, ob diese Version korrekt ist oder ob du weitere Probleme siehst.

SDwarfs
quelle
Gute Arbeit! Leider ist y [n] = y [n-2] * b + x [n-1] funktional nicht äquivalent zu y [n] = y [n-1] * b + x [n] mit Latenz. Die Form einer IIR-Übertragungsfunktion sieht tatsächlich so aus: y [n] = x [n] * b0 + x [n-1] * b1 - y [n-1] * a1 - y [n-2] * a2 und so weiter. Ihr Formular setzt b0 und a1 auf 0 und verwendet stattdessen b1 und a2. Diese Transformation erzeugt jedoch tatsächlich einen ganz anderen Filter. Wenn es eine Möglichkeit gäbe, einen Filter zu berechnen, bei dem der erste Nenner (a1) auf Null gesetzt ist, würden beide Lösungen perfekt funktionieren.
Marcus10110
Nun, Sie müssen das Problem der "eingeführten Verzögerung" richtig verstehen. Beispielsweise sollte ein Filter "Datenstromverarbeitung" seine Eingabe nur weiterleiten, da y [n] = x [n] korrekt funktionieren würde, wenn er y [n] = x [n-1] als Ausgabe erzeugt. Die Ausgabe wird nur um 1 Zyklus verzögert (z. B. wird der Ausgabeindex relativ zu allen Eingangsindizes um einen festen Wert versetzt)! In unserem Beispiel bedeutet dies, dass Ihre Funktion y[n+l] = y[n-1] * b + x[n]einen festen Wert für die Verzögerung hat, lder umgeschrieben werden kann, y[n] = y[n-1-l] * b + x[n-l]und für l = 1 ist dies y[n] = y[n-2] * b + x[n-1].
SDwarfs
Für Ihren komplexeren IIR-Filter müssten Sie dasselbe tun: y[n+l] = x[n] * b0 + x[n-1] * b1 - y[n-1] * a1 - y[n-2] * a2=> y[n] = x[n-l]*b0 + x[n-1-l] * b1 - y[n-1-l] * a1 - y[n-2-l]*a2. Angenommen, Sie können alle drei Multiplikationen parallel durchführen (1. Stufe / 1 Zyklus) und müssen die beiden addieren, um die Produkte zu addieren. Sie benötigen 2 Zyklen (1 Zyklus: Add / Sub, erste zwei Produktergebnisse, 1 Zyklus: Add / Sub Als Ergebnis dieser beiden Add / Subs) benötigen Sie 2 zusätzliche Zyklen. Also l = (3-1) = 2 gibt dir y[n]=x[n-2]*b0+x[n-1-2]*b1-y[n-1-2]*a1-y[n-2-2]*a2=>y[n]=x[n-2]*b0+x[n-3]*b1-y[n-3]*a1-y[n-4]*a2
SDwarfs
Damit dies funktioniert, muss Ihr FPGA natürlich in der Lage sein, parallel zu arbeiten: 4 Multiplikationen und 3 Additionen / Subtraktionen. Das heißt, Sie benötigen Ressourcen für 4 Multiplikatoren und 3 Addierer.
SDwarfs
0

Ja, Sie können mit der Abtastfrequenz takten.

Eine Lösung für dieses Problem besteht darin, den ursprünglichen Ausdruck so zu bearbeiten, dass Pipeline-Register eingefügt werden können, während die gewünschte Ausgabesequenz beibehalten wird.

Gegeben: y [n] = y [n-1] * b1 + x [n];

Dies kann manipuliert werden in: y [n] = y [n-2] * b1 * b1 + x [n-1] * b1 + x [n].

Um zu überprüfen, ob dies dieselbe Sequenz ist, betrachten Sie, was mit den ersten mehreren Abtastwerten x [0], x [1], x [2] usw. passiert, wobei vor x [0] alle x, y-Abtastwerte Null waren.

Für den ursprünglichen Ausdruck lautet die Sequenz:

y = x[0],

x[1] +x[0]*b1,

x[2] +x[1]*b1 +x[0]*b1*b1,

x[3] +x[2]*b1 +x[1]*b1*b1 +x[0]*b1*b1*b1, ...

Es ist klar, dass es notwendig ist, dass b1 <1 ist, sonst wächst dies ungebunden.

Betrachten Sie nun den manipulierten Ausdruck:

y = x[0],

x[0]*b1 +x[1],

x[0]*b1*b1 +x[1]*b1 +x[2],

x[0]*b1*b1*b1 +x[1]*b1*b1 +x[2]*b1 +x[3], ...

Dies ist die gleiche Reihenfolge.

Eine Hardwarelösung in Grundelementen der Xilinx-Bibliothek würde zwei DSP48E in Kaskade benötigen. In Abbildung 1-1 in UG193 v3.6 finden Sie die folgenden Port- und Registernamen. Das erste Grundelement multipliziert mit b1 und addiert einen Takt später; Die zweite wird mit b1 * b1 multipliziert und eine Uhr später addiert. Für diese Logik gibt es eine Pipeline-Latenz von 4 Takten.

- DSP48E # 1

a_port1: = b1; - konstanter Koeffizient, setze AREG = 1

b_port1: = x; - setze Attribut BREG = 1

c_port1: = x; - CREG = 1 setzen

- intern in DSP48E # 1

reg_a1 <= a_port1;

reg_b1 <= b_port1;

reg_c1 ​​<= c_port1;

reg_m1 <= reg_a1 * reg_b1;

reg_p1 <= reg_m1 + reg_c1; - Ausgabe des 1. DSP48E

- Ende von DSP48E # 1

- DSP48E # 2

a_port2: = reg_p2; - setze Attribut AREG = 0

                -- this means the output of register reg_p2

                -- directly feeds back to the multiplier

b_port2: = b1 * b1; - konstant, setze BREG = 1

c_port2: = reg_p1; - CREG = 1 setzen

- intern in DSP48E # 2

reg_b2 <= b_port2;

reg_c2 <= c_port2;

reg_m2 <= a_port2 * reg_b2;

reg_p2 <= reg_m2 + reg_c2;

- Ende von DSP48E # 2

Die Sequenz bei reg_p1:

x [0],

x [1] + x [0] * b1,

x [2] + x [1] * b1,

x [3] + x [2] * b1,

usw.

Die Sequenz bei reg_p2 ist das gewünschte Ergebnis. Innerhalb des 2. DSP48E hat das Register reg_m2 eine Sequenz:

x [0] * b1 * b1,

x [1] * b1 * b1 + x [0] * b1 * b1 * b1,

x [2] * b1 * b1 + x [1] * b1 * b1 * b1 + x [0] * b1 * b1 * b1 * b1

Dieses Ergebnis hat eine schöne Eleganz. Es ist klar, dass der DSP48E nicht im selben Takt multipliziert und addiert, aber genau das erfordert die Differenzgleichung. Die manipulierte Differenzgleichung ermöglicht es uns, die M- und P-Register im DSP48E zu tolerieren und mit voller Geschwindigkeit zu takten.

Dave Brown
quelle