Bei dieser Frage geht es um die Implementierung eines IIR-Filters in einem FPGA mit DSP-Slices mit sehr spezifischen Kriterien.
Angenommen, Sie erstellen einen Filter ohne Vorwärtsabgriffe und nur 1 Rückwärtsabgriff mit dieser Gleichung:
(siehe Bild)
Nehmen Sie als Beispiel den DSP48A1-Slice von Xilinx – die meisten Hard-IP-DSP-Slices sind ähnlich.
Angenommen, Sie haben analoge Daten, die mit 1 Abtastung pro Takt eingehen. Ich möchte einen IIR-Filter entwerfen, der synchron zum Abtasttakt läuft.
Das Problem ist, dass Sie, um den DSP-Slice mit der maximalen Rate laufen zu lassen, nicht im selben Zyklus multiplizieren UND addieren können. Zwischen diesen Komponenten muss ein Pipelineregister vorhanden sein.
Wenn Sie also bei jedem Takt 1 neues Sample haben, müssen Sie 1 Ausgang pro Takt erzeugen. Allerdings benötigt man die bisherige Ausgabe 2 Takte bevor man eine neue in dieser Ausführung herstellen kann.
Die offensichtliche 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 vollständig gepipelineten DSP-Slice abtasten, ist leider keine dieser Lösungen möglich. Gibt es eine andere Möglichkeit das zu bauen?
(Bonuspunkte, wenn Sie einen IIR-Filter entwerfen können, der mit einer beliebigen Anzahl von DSP-Slices mit der halben Abtastrate arbeitet)
Das Ziel wäre, einen Kompensationsfilter für einen 1-GSPS-ADC in einem Xilinx Artix FPGA zu betreiben. Ihre DSP-Slices können bei vollständiger Pipeline mit knapp über 500 MHz laufen. Wenn es eine Lösung für 1 Sample pro Takt gibt, würde ich gerne versuchen, die Lösung für 2 Samples pro Takt zu skalieren. Mit einem FIR-Filter geht das alles ganz einfach.
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 jeden Eingangsabtastwert 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 die Multiplikation des aktuellen Samples UND die Summation des Multiplikationsergebnisses des letzten Samples parallel durchführen. Dies verursacht eine konstante 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 so 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 "output" in der zweiten Zeile daher die Ausgabe des letzten Taktzyklus verwendet!
Ich habe nicht viel mit Verilog gearbeitet, daher ist die Syntax dieses Codes höchstwahrscheinlich falsch (zB fehlende Bitbreite von Ein-/Ausgangssignalen; Ausführungssyntax für 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: Falls Ihr Faktor "b1" eine feste Konstante ist, können Sie das Design möglicherweise optimieren, indem Sie einen speziellen Multiplikator implementieren, der nur eine skalare Eingabe akzeptiert und nur "mal b1" berechnet.
Ja, das war eigentlich richtig für die folgende alte (FALSCHE!!!) 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 die Eingabewerte auch 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 bei den ersten paar Zyklen passiert. Beachten Sie, dass die ersten 2 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 Zyklus n = 2 die folgende Ausgabe erhalten:
y[2]=y[0]*b+x[1]
y[3]=y[1]*b+x[2]
was äquivalent ist
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 Lag l=1 verzögert wird. Das heißt, die Ausgabedaten sind äquivalent, aber um einen "Index" verzögert. Um es klarer zu machen: Die Ausgangsdaten werden um 2 Zyklen verzögert, 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 den Datenfluss grafisch darzustellen:
PS: Vielen Dank, dass Sie sich meinen Code genau angesehen haben. Also habe ich auch was gelernt! ;-) Lassen Sie mich wissen, ob diese Version korrekt ist oder ob Sie weitere Probleme sehen.
y[n+l] = y[n-1] * b + x[n]
einen festen Wert für die Verzögerung hat l
, der 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]
.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. Schritt / 1 Zyklus) und müssen die Produkte zusammenzählen, benötigen Sie 2 Zyklen (1 Zyklus: Addieren/Subtrahieren der ersten beiden Produktergebnisse, 1 Zyklus: Addieren/Subtrahieren das Ergebnis dieser beiden Add/Subs), benötigen Sie 2 zusätzliche Zyklen. Also l=(3-1)=2 ergibt 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
Ja, Sie können mit der Abtastfrequenz takten.
Eine Lösung für dieses Problem besteht darin, den ursprünglichen Ausdruck so zu manipulieren, dass Pipeline-Register eingefügt werden können, während die gewünschte Ausgangssequenz beibehalten wird.
Gegeben: y[n] = y[n-1]*b1 +x[n];
dies kann manipuliert werden zu: y[n] = y[n-2]*b1*b1 +x[n-1]*b1 +x[n].
Um zu überprüfen, ob dies die gleiche 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 unbegrenzt.
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 Xilinx-Bibliotheksprimitiven würde zwei DSP48E in Kaskade benötigen. Siehe Abbildung 1-1 in UG193 v3.6 für die Port- und Registernamen unten. Das erste Primitiv multipliziert mit b1 und addiert einen Takt später; die zweite multipliziert mit b1*b1 und addiert einen Takt später. Für diese Logik gibt es eine Pipeline-Latenzzeit von 4 Takten.
-- DSP48E #1
a_port1 := b1; -- konstanter Koeffizient, setze AREG=1
b_port1 := x; -- Attribut BREG=1 setzen
c_port1 := x; -- CREG=1 setzen
-- intern für 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; -- Attribut AREG=0 setzen
-- this means the output of register reg_p2
-- directly feeds back to the multiplier
b_port2 := b1*b1; -- Konstante, setze BREG=1
c_port2 := reg_p1; -- CREG=1 setzen
-- intern für 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. Intern im 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. Natürlich multipliziert und addiert der DSP48E nicht denselben Takt, aber das ist es, was die Differenzgleichung erfordert. Die manipulierte Differenzgleichung ermöglicht es uns, die M- und P-Register im DSP48E zu tolerieren und mit voller Geschwindigkeit zu takten.
horta
Marcus10110
horta
jbarlow
Marcus10110
horta
Martin Thomson
Marcus10110