To pytanie dotyczy implementacji filtra IIR w układzie FPGA z segmentami DSP, z bardzo szczegółowymi kryteriami.
Powiedzmy, że tworzysz filtr bez stuknięć w przód i tylko 1 stuknij w tył, z tym równaniem:
(patrz zdjęcie)
Weźmy na przykład wycinek DSP48A1 z Xilinx - większość twardych wycinków IP DSP jest podobna.
Powiedzmy, że masz dane analogowe przychodzące z 1 próbką na zegar. Chciałbym zaprojektować filtr IIR, który będzie działał synchronicznie z zegarem próbki.
Problem polega na tym, że aby uruchomić wycinek DSP z maksymalną szybkością, nie można pomnożyć ORAZ dodać w tym samym cyklu. Musisz mieć rejestr potoku między tymi komponentami.
Tak więc, jeśli masz 1 nową próbkę na każdy zegar, będziesz musiał wyprodukować 1 wyjście na zegar. Jednak potrzebne są poprzednie zegary wyjściowe 2, aby można było wyprodukować nowy w tym projekcie.
Oczywistym rozwiązaniem jest albo przetwarzanie danych z podwójną częstotliwością zegara, albo wyłączenie rejestru potoku, aby można było pomnożyć i dodać w tym samym cyklu.
Niestety, jeśli powiesz, że próbujesz z maksymalną częstotliwością taktowania w pełni potokowego wycinka DSP, żadne z tych rozwiązań nie jest możliwe. Czy jest jakiś inny sposób na zbudowanie tego?
(Punkty bonusowe, jeśli możesz zaprojektować filtr IIR, który działa z połową częstotliwości próbkowania, używając dowolnej liczby wycinków DSP)
Celem byłoby uruchomienie filtra kompensacyjnego dla ADC 1 GSPS w układzie Xilinx Artix FPGA. Ich segmenty DSP mogą działać nieco ponad 500 MHz, gdy są w pełni potokowe. Jeśli istnieje rozwiązanie dla 1 próbki na zegar, chciałbym spróbować skalować rozwiązanie dla 2 próbek na zegar. Wszystko to jest bardzo łatwe dzięki filtrowi FIR.
Odpowiedzi:
Nie pracowałem jeszcze z filtrami IIR, ale jeśli potrzebujesz tylko obliczyć podane równanie
raz na cykl CPU możesz użyć potokowania.
W jednym cyklu wykonujesz mnożenie, aw jednym cyklu musisz wykonać sumowanie dla każdej próbki wejściowej. Oznacza to, że Twój układ FPGA musi być w stanie wykonać pomnożenie w jednym cyklu, jeśli jest taktowany przy danej częstotliwości próbkowania! Następnie wystarczy wykonać pomnożenie bieżącej próbki ORAZ sumowanie wyniku pomnożenia ostatniej próbki równolegle. Spowoduje to stałe opóźnienie przetwarzania o 2 cykle.
Ok, rzućmy okiem na formułę i zaprojektuj potok:
Twój kod potoku może wyglądać następująco:
Zauważ, że wszystkie trzy polecenia muszą być wykonywane równolegle, a zatem „wyjście” w drugim wierszu wykorzystuje dane wyjściowe z ostatniego cyklu zegara!
Nie pracowałem dużo z Verilog, więc składnia tego kodu jest najprawdopodobniej niepoprawna (np. Brak szerokości bitów sygnałów wejściowych / wyjściowych; składnia wykonania dla mnożenia). Powinieneś jednak pomyśleć:
PS: Być może jakiś doświadczony programista Verilog mógłby edytować ten kod i później usunąć ten komentarz i komentarz nad kodem. Dzięki!
PPS: W przypadku, gdy twój współczynnik „b1” jest stałą, możesz być w stanie zoptymalizować projekt poprzez wdrożenie specjalnego mnożnika, który przyjmuje tylko jedno wejście skalarne i oblicza tylko „czasy b1”.
Odpowiedź na: „Niestety, w rzeczywistości jest to równoważne y [n] = y [n-2] * b1 + x [n]. Wynika to z dodatkowego etapu potoku.” jako komentarz do starej wersji odpowiedzi
Tak, tak naprawdę było to właściwe dla następującej starej (NIEPRAWIDŁOWEJ) wersji:
Mam nadzieję, że poprawiłem ten błąd, opóźniając wartości wejściowe również w drugim rejestrze:
Aby upewnić się, że tym razem działa poprawnie, zobaczmy, co dzieje się w pierwszych kilku cyklach. Zauważ, że pierwsze 2 cykle generują mniej lub więcej (zdefiniowanych) śmieci, ponieważ żadne poprzednie wartości wyjściowe (np. Y [-1] == ??) nie są dostępne. Rejestr y jest inicjalizowany wartością 0, co jest równoznaczne z przyjęciem y [-1] == 0.
Pierwszy cykl (n = 0):
Drugi cykl (n = 1):
Trzeci cykl (n = 2):
Czwarty cykl (n = 3):
Widzimy, że zaczynając od cylce n = 2 otrzymujemy następujące dane wyjściowe:
co jest równoważne z
Jak wspomniano powyżej, wprowadzamy dodatkowe opóźnienie wynoszące 1 = 1 cykli. Oznacza to, że twoje wyjście y [n] jest opóźnione o opóźnienie l = 1. Oznacza to, że dane wyjściowe są równoważne, ale są opóźnione o jeden „indeks”. Aby być bardziej zrozumiałym: dane wyjściowe są opóźnione o 2 cykle, ponieważ potrzebny jest jeden (normalny) cykl zegara i dodawany jest 1 dodatkowy (opóźnienie 1 = 1) cykl zegara dla etapu pośredniego.
Oto szkic, aby graficznie zobrazować przepływ danych:
PS: Dziękuję za dokładne przyjrzenie się mojemu kodowi. Więc też się czegoś nauczyłem! ;-) Daj mi znać, czy ta wersja jest poprawna lub jeśli pojawią się kolejne problemy.
źródło
y[n+l] = y[n-1] * b + x[n]
stałą wartość opóźnienia,l
którą można przepisać do,y[n] = y[n-1-l] * b + x[n-l]
a dla l = 1 to jesty[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
. Zakładając, że możesz wykonać wszystkie trzy multiplikacje równolegle (1. etap / 1 cykl) i musisz zrobić, aby dodać produkty razem, potrzebujesz 2 cykli (1 cykl: dodaj / sub pierwsze dwa wyniki produktu, 1 cykl: dodaj / sub wynik tych dwóch dodań / subskrypcji), będziesz potrzebować 2 dodatkowych cykli. Więc l = (3-1) = 2 dajey[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
Tak, możesz taktować częstotliwość próbkowania.
Rozwiązaniem tego problemu jest manipulowanie pierwotnym wyrażeniem, aby można było wstawić rejestry potoku, zachowując pożądaną sekwencję wyjściową.
Biorąc pod uwagę: y [n] = y [n-1] * b1 + x [n];
można to zmienić na: y [n] = y [n-2] * b1 * b1 + x [n-1] * b1 + x [n].
Aby sprawdzić, czy jest to ta sama sekwencja, zastanów się, co dzieje się z pierwszymi kilkoma próbkami x [0], x [1], x [2] itd., Gdzie przed x [0] wszystkie próbki x, y były zerowe.
Dla oryginalnego wyrażenia sekwencja jest następująca:
Oczywiste jest, że b1 <1, w przeciwnym razie wzrośnie to bez ograniczeń.
Rozważ teraz zmanipulowane wyrażenie:
To jest ta sama sekwencja.
Rozwiązanie sprzętowe w prymitywach bibliotek Xilinx wymagałoby dwóch kaskadowych DSP48E. Patrz rysunek 1-1 w UG193 v3.6 dla nazw portów i rejestrów poniżej. Pierwsza operacja podstawowa mnoży się przez b1 i dodaje jeden zegar później; drugi mnoży się przez b1 * b1 i dodaje jeden zegar później. Dla tej logiki występuje opóźnienie 4-liniowe.
- DSP48E # 1
a_port1: = b1; - stały współczynnik, ustaw AREG = 1
b_port1: = x; - ustaw atrybut BREG = 1
c_port1: = x; - ustaw CREG = 1
- wewnętrzny do DSP48E # 1
reg_a1 <= port_1;
reg_b1 <= b_port1;
reg_c1 <= c_port1;
reg_m1 <= reg_a1 * reg_b1;
reg_p1 <= reg_m1 + reg_c1; - wyjście 1. DSP48E
- koniec DSP48E # 1
- DSP48E # 2
a_port2: = reg_p2; - ustaw atrybut AREG = 0
b_port2: = b1 * b1; - stała, ustaw BREG = 1
c_port2: = reg_p1; - ustaw CREG = 1
- wewnętrzny do DSP48E # 2
reg_b2 <= b_port2;
reg_c2 <= c_port2;
reg_m2 <= port_2 * reg_b2;
reg_p2 <= reg_m2 + reg_c2;
- koniec DSP48E # 2
Sekwencja w reg_p1:
x [0],
x [1] + x [0] * b1,
x [2] + x [1] * b1,
x [3] + x [2] * b1,
itp.
Sekwencja w reg_p2 jest pożądanym wynikiem. Wewnątrz 2. DSP48E rejestr reg_m2 ma sekwencję:
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
Ten wynik ma niezłą elegancję. Oczywiście DSP48E nie mnoży się i nie dodaje tego samego zegara, ale tego właśnie wymaga równanie różnicy. Manipulowane równanie różnicy pozwala nam tolerować rejestry M i P w DSP48E i zegarze z pełną prędkością.
źródło