metody obliczania stałego punktu atan2 na FPGA

12

Potrzebuję przetwarzania atan2(x,y)na FPGA z ciągłym strumieniem danych wejściowych / wyjściowych. Udało mi się to zaimplementować za pomocą rozwijanego, potokowego jądra CORDIC, ale aby uzyskać wymaganą dokładność, musiałem wykonać 32 iteracje. Doprowadziło to do poświęcenia dość dużej liczby LUT temu jednemu zadaniu. Próbowałem zmienić przepływ, aby używać częściowo rozwiniętych jąder CORDIC, ale potem potrzebowałem zwielokrotnionej częstotliwości taktowania, aby wykonywać powtarzane pętle, zachowując ciągły przepływ wejścia / wyjścia. Dzięki temu nie mogłem dotrzymać terminu.

Teraz sięgam po alternatywne sposoby obliczeń atan2(x,y).

Pomyślałem o użyciu tabel przeglądowych bloków RAM z interpolacją, ale ponieważ istnieją 2 zmienne, potrzebowałbym 2 wymiarów tabel przeglądowych, a to jest bardzo zasobochłonne pod względem wykorzystania blokowych pamięci RAM.

Potem pomyślałem o użyciu faktu atan2(x,y)związanego atan(x/y)z dostosowaniem kwadrantu. Problem polega na tym, że x/ywymaga prawdziwego podziału, ponieważ ynie jest stały, a podziały na układy FPGA są bardzo wymagające pod względem zasobów.

Czy są jakieś nowatorskie sposoby implementacji atan2(x,y)na FPGA, które skutkowałyby mniejszym wykorzystaniem LUT, ale nadal zapewniałyby dobrą dokładność?

użytkownik2913869
źródło
2
Jaka jest częstotliwość taktowania przetwarzania i szybkość danych wejściowych?
Jim Clay
Jakiej dokładności potrzebujesz? Zakładam, że używasz obliczeń o stałym punkcie. Jakiej głębi bitowej używasz? Aproksymacja wielomianowa (lub LUT) z korektą kwadrantu jest powszechną metodą do wdrożenia atan2. Nie jestem jednak pewien, czy dasz sobie radę bez podziału.
Jason R
Zegar wejściowy wynosi 150 MHz, szybkość wejściowa wynosi 150 MSamp / s. Zasadniczo otrzymuję nowe dane wejściowe w każdym cyklu zegara. Opóźnienie jest w porządku, ale muszę również generować wyjście przy 150 MSampach / sek.
user2913869,
Moje symulacje pokazują, że mogę żyć z około 1 * 10 ^ -9. Nie jestem pewien absolutnych minimalnych bitów punktu stałego, ale symulowałem z formatem punktu stałego
Q10.32
W tym artykule opisano stałą implementację atan2. Nadal jednak będziesz potrzebować podziału.
Matt L.

Odpowiedzi:

20

Możesz użyć logarytmów, aby pozbyć się podziału. Dla (x,y) w pierwszej ćwiartce:

z=log2(y)log2(x)atan2(y,x)=atan(y/x)=atan(2z)

atan (2 ^ z)

Rycina 1. Wykres atan(2z)

Aby uzyskać wymaganą dokładność 1E-9, musisz zbliżyć atan(2z) w zakresie 30<z<30 . Możesz skorzystać z symetrii atan(2z)=π2atan(2z)lub alternatywnie upewnij się, że(x,y)jest w znanej liczbie oktanowej. Aby przybliżyćlog2(a):

b=floor(log2(a))c=a2blog2(a)=b+log2(c)

b można obliczyć, znajdując lokalizację najbardziej znaczącego niezerowego bitu. c można obliczyć przez przesunięcie bitowe. Konieczne byłoby przybliżenielog2(c) w zakresie1c<2 .

log2 (c)

Ryc. 2. Wykres log2(c)

Dla twoich wymagań dokładności wystarczy interpolacja liniowa i jednorodne próbkowanie, 214+1=16385 próbek log2(c) i 30×212+1=122881 próbek atan(2z) dla 0<z<30 . Ten drugi stół jest dość duży. Dzięki niemu błąd spowodowany interpolacją zależy w dużej mierze od z :

Błąd aproksymacji atan (2 ^ z)

Ryc. 3. największy błąd bezwzględny aproksymacji atan(2z) dla różnych zakresów z (oś pozioma) dla różnych liczb próbek (od 32 do 8192) na przedział jednostkowy z . Największy błąd bezwzględny dla 0z<1 (pominięty) jest nieco mniejszy niż dla floor(log2(z))=0 .

atan(2z) tabela może być podzielony na wiele subtables które odpowiadają 0z<1 i innym floor(log2(z)) z z1 , który jest łatwy do obliczenia. Długości tabeli można wybrać zgodnie z rys. 3. Indeks wewnątrz podtabeli można obliczyć za pomocą prostej manipulacji ciągiem bitów. Dla potrzeb dokładności atan(2z) będą zawierały łącznie 29217 próbek, jeśli rozszerzysz zakres z do 0z<32 dla uproszczenia.

Do późniejszego wykorzystania oto niezgrabny skrypt Pythona, którego użyłem do obliczenia błędów aproksymacji:

from numpy import *
from math import *
N = 10
M = 20
x = array(range(N + 1))/double(N) + 1
y = empty(N + 1, double)
for i in range(N + 1):
    y[i] = log(x[i], 2)

maxErr = 0
for i in range(N):
    for j in range(M):
        a = y[i] + (y[i + 1] - y[i])*j/M
        if N*M < 1000: 
            print str((i*M + j)/double(N*M) + 1) + ' ' + str(a)
        b = log((i*M + j)/double(N*M) + 1, 2)
        err = abs(a - b)
        if err > maxErr:
            maxErr = err

print maxErr

y2 = empty(N + 1, double)
for i in range(1, N):
    y2[i] = -1.0/16.0*y[i-1] + 9.0/8.0*y[i] - 1.0/16.0*y[i+1]


y2[0] = -1.0/16.0*log(-1.0/N + 1, 2) + 9.0/8.0*y[0] - 1.0/16.0*y[1]
y2[N] = -1.0/16.0*y[N-1] + 9.0/8.0*y[N] - 1.0/16.0*log((N+1.0)/N + 1, 2)

maxErr = 0
for i in range(N):
    for j in range(M):
        a = y2[i] + (y2[i + 1] - y2[i])*j/M
        b = log((i*M + j)/double(N*M) + 1, 2)
        if N*M < 1000: 
            print a
        err = abs(a - b)
        if err > maxErr:
            maxErr = err

print maxErr

y2[0] = 15.0/16.0*y[0] + 1.0/8.0*y[1] - 1.0/16.0*y[2]
y2[N] = -1.0/16.0*y[N - 2] + 1.0/8.0*y[N - 1] + 15.0/16.0*y[N]

maxErr = 0
for i in range(N):
    for j in range(M):
        a = y2[i] + (y2[i + 1] - y2[i])*j/M
        b = log((i*M + j)/double(N*M) + 1, 2)
        if N*M < 1000: 
            print str(a) + ' ' + str(b)
        err = abs(a - b)
        if err > maxErr:
            maxErr = err

print maxErr

P = 32
NN = 13
M = 8
for k in range(NN):
    N = 2**k
    x = array(range(N*P + 1))/double(N)
    y = empty((N*P + 1, NN), double)
    maxErr = zeros(P)
    for i in range(N*P + 1):
        y[i] = atan(2**x[i])

    for i in range(N*P):
        for j in range(M):
            a = y[i] + (y[i + 1] - y[i])*j/M
            b = atan(2**((i*M + j)/double(N*M)))
            err = abs(a - b)
            if (i*M + j > 0 and err > maxErr[int(i/N)]):
                maxErr[int(i/N)] = err

    print N
    for i in range(P):
        print str(i) + " " + str(maxErr[i])    

Lokalna maksymalna błędu z zbliżony funkcję f(x) przez interpolację liniową f ( x ) z próbek f ( x ) , wykonane z jednolitego próbek z próbek przedział Δ x może być w przybliżeniu analitycznie:f^(x)f(x)Δx

f^(x)f(x)(Δx)2limΔx0f(x)+f(x+Δx)2f(x+Δx2)(Δx)2=(Δx)2f(x)8,

f(x)f(x)x

atan^(2z)atan(2z)(Δz)22z(14z)ln(2)28(4z+1)2,log2^(a)log2(a)(Δa)28a2ln(2).

Ponieważ funkcje są wklęsłe, a próbki pasują do funkcji, błąd jest zawsze w jednym kierunku. Lokalny maksymalny błąd bezwzględny można by zmniejszyć o połowę, gdyby znak błędu pojawiał się naprzemiennie tam iz powrotem raz na każdy interwał próbkowania. Dzięki interpolacji liniowej można uzyskać prawie optymalne wyniki, wstępnie filtrując każdą tabelę poprzez:

y[k]={b0x[k]+b1x[k+1]+b2x[k+2]if k=0,c1x[k1]+c0x[k]+c1x[k+1]if 0<k<N,b2x[k2]+b1x[k1]+b0x[k]if k=N,

xy0kNc0=98,c1=116,b0=1516,b1=18,b2=116c0,c1N

(Δx)NlimΔx0(c1f(xΔx)+c0f(x)+c1f(x+Δx))(1a)+(c1f(x)+c0f(x+Δx)+c1f(x+2Δx))af(x+aΔx)(Δx)N={(c0+2c11)f(x)if N=0,|c1=1c020if N=1,1+aa2c02(Δx)2f(x)if N=2,|c0=98

0a<1f(x)f(x)=exb0,b1,b2

(Δx)NlimΔx0(b0f(x)+b1f(x+Δx)+b2f(x+2Δx))(1a)+(c1f(x)+c0f(x+Δx)+c1f(x+2Δx))af(x+aΔx)(Δx)N={(b0+b1+b21+a(1b0b1b2))f(x)if N=0,|b2=1b0b1(a1)(2b0+b12)Δxf(x)if N=1,|b1=22b0(12a2+(2316b0)a+b01)(Δx)2f(x)if N=2,|b0=1516

0a<1

Błąd aproksymacji z filtrem wstępnym i bez oraz kondycjonowanie końcowe

log2(a)

W tym artykule prawdopodobnie przedstawiono bardzo podobny algorytm: R. Gutierrez, V. Torres i J. Valls, „ Implementacja FPGA atan (Y / X) w oparciu o transformację logarytmiczną i techniki oparte na LUT ”, Journal of Systems Architecture , tom . 56, 2010. Streszczenie mówi, że ich implementacja przewyższa poprzednie algorytmy oparte na CORDIC pod względem prędkości, a algorytmy oparte na LUT pod względem wielkości powierzchni.

Olli Niemitalo
źródło
3
Matthew Gambrell i ja dokonaliśmy inżynierii wstecznej układu dźwiękowego Yamaha YM3812 z 1985 roku (pod mikroskopem) i znaleźliśmy w nim podobne tabele pamięci ROM / log tylko do odczytu. Yamaha zastosowała dodatkową sztuczkę, zastępując co drugi wpis w każdej tabeli różnicą w stosunku do poprzedniego wpisu. Dla płynnych funkcji różnica wymaga mniejszej liczby bitów i obszaru chipa do reprezentacji niż funkcja. Mieli już dodatek na chipie, którego mogli użyć, aby dodać różnicę do poprzedniego wpisu.
Olli Niemitalo
3
Dziękuję Ci bardzo! Uwielbiam tego rodzaju exploity właściwości matematycznych. Zdecydowanie opracuję kilka symulacji MATLAB i jeśli wszystko wygląda dobrze, przejdź do HDL. Kiedy wszystko się skończy, zgłoś moje oszczędności LUT.
user2913869
Użyłem twojego opisu jako przewodnika i cieszę się, że zostałem zredukowany przez LUT o prawie 60%. Musiałem zmniejszyć BRAMy, więc doszedłem do wniosku, że mogę uzyskać stały błąd maksymalny w mojej tabeli ATAN, wykonując nierównomierne próbkowanie: miałem wiele LRAM BRAM (ta sama liczba bitów adresu), im bliżej zero, tym szybsze próbkowanie. Wybrałem zakresy moich tabel jako potęgi 2, dzięki czemu mogłem łatwo wykryć, w jakim zakresie byłem, i wykonywać automatyczne indeksowanie tabel za pomocą operacji bitowych. Zastosowałem również symetrię atan, więc zapisałem tylko połowę kształtu fali.
user2913869
Mogłem też pominąć niektóre z twoich edycji, ale udało mi się zaimplementować 2 ^ z, dzieląc go na 2 ^ {if} = 2 ^ i * 2 ^ {0.f}, gdzie i jest liczbą całkowitą if część ułamkowa. 2 ^ i jest proste, tylko bitowa manipulacja, a 2 ^ {0.f} miał ograniczony zasięg, więc dobrze nadawał się do LUT z interpolacją. Zajmowałem się również przypadkiem ujemnym: 2 ^ {- if} = 2 ^ {- i} * 1 / (2 ^ {0.f}. Więc jeszcze jedna tabela dla 1/2 ^ {0.f}. Mój następny krok może być zastosowanie mocy 2 zakresowego / nierównomiernego próbkowania na LUT log2 (y), ponieważ wydaje się, że byłby to idealny kandydat na przebieg tego rodzaju. Pozdrawiam!
użytkownik2913869
1
Lol, tak, całkowicie przegapiłem ten krok. Spróbuję teraz. Powinny ocalić mi jeszcze więcej LUT i jeszcze więcej pamięci BRAM
user2913869