metodi di calcolo in virgola fissa atan2 su FPGA


12

Ho bisogno di elaborare atan2(x,y)un FPGA con un flusso continuo di dati di input / output. Sono riuscito a implementarlo usando kernel CORDIC srotolati e pipeline, ma per ottenere l'accuratezza di cui ho bisogno ho dovuto eseguire 32 iterazioni. Ciò ha portato a un numero piuttosto elevato di LUT dedicati a questo compito. Ho provato a cambiare il flusso per usare i kernel CORDIC parzialmente srotolati, ma poi avevo bisogno di una frequenza di clock moltiplicata per eseguire loop ripetuti mantenendo comunque un flusso di input / output continuo. Con questo, non sono riuscito a rispettare i tempi.

Quindi ora sto cercando modi alternativi di elaborazione atan2(x,y).

Ho pensato di utilizzare le tabelle di ricerca della RAM a blocchi con interpolazione, ma dato che ci sono 2 variabili avrei bisogno di 2 dimensioni delle tabelle di ricerca, e questo richiede molte risorse in termini di utilizzo della RAM a blocchi.

Ho quindi pensato di utilizzare il fatto atan2(x,y)relativo alla atan(x/y)regolazione del quadrante. Il problema con questo è che ha x/ybisogno di una vera divisione poiché ynon è una costante e le divisioni sugli FPGA richiedono molta risorse.

Esistono altri modi nuovi di implementare atan2(x,y)su un FPGA che porterebbe a un minore utilizzo di LUT, ma che comunque forniscono una buona precisione?


2
Qual è la frequenza di elaborazione e la velocità dei dati di input?
Jim Clay,

Qual è la precisione richiesta? Presumo anche che tu stia utilizzando il calcolo a virgola fissa. Che profondità di bit stai usando? Un'approssimazione polinomiale (o LUT) con regolazione del quadrante è un metodo comune da implementare atan2. Non sono sicuro se puoi cavartela senza una divisione, però.
Jason R,

Il clock di input è di 150 MHz, la velocità dei dati di input è di 150 MSamps / sec. Praticamente ricevo un nuovo input ad ogni ciclo di clock. Avere la latenza va bene, ma devo anche produrre un output a 150 MSamps / sec.
user2913869,

Le mie simulazioni mostrano che posso vivere con circa 1 * 10 ^ -9. Non sono sicuro dei bit minimi assoluti in virgola fissa, ma ho simulato con un formato in virgola fissa
Q10.32

Questo articolo spiega un'implementazione a virgola fissa di atan2. Avrai comunque bisogno di una divisione.
Matt L.

Risposte:


20

Puoi usare i logaritmi per sbarazzarti della divisione. Per nel primo quadrante:(x,y)

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

atan (2 ^ z)

Figura 1. Grafico diatan(2z)

Dovresti approssimare nell'intervallo per ottenere la precisione richiesta di 1E-9. Puoi sfruttare la simmetria o in alternativa assicurarti che è in un ottante noto. Per approssimare :atan(2z)30<z<30atan(2z)=π2atan(2z)(x,y)log2(a)

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

b può essere calcolato trovando la posizione del bit diverso da zero più significativo. può essere calcolato con un bit shift. Dovresti approssimare nell'intervallo .clog2(c)1c<2

log2 (c)

Figura 2. Grafico dilog2(c)

Per i requisiti di precisione, interpolazione lineare e campionamento uniforme, campioni di e campioni di per dovrebbe essere sufficiente. Quest'ultimo tavolo è piuttosto grande. Con esso, l'errore dovuto all'interpolazione dipende molto da :214+1=16385log2(c)30×212+1=122881atan(2z)0<z<30z

Errore di approssimazione atan (2 ^ z)

Figura 3. Errore assoluto maggiore approssimazione approssimazione per diversi intervalli di (asse orizzontale) per diversi numeri di campioni (da 32 a 8192) per intervallo di unità di . L'errore assoluto più grande per (omesso) è leggermente inferiore rispetto a .atan(2z)zz0z<1floor(log2(z))=0

La tabella può essere suddivisa in più sottotitoli che corrispondono a e diversi con , che è facile calcolare. Le lunghezze della tabella possono essere scelte come guidato dalla Fig. 3. L'indice all'interno della sottotabella può essere calcolato con una semplice manipolazione della stringa di bit. Per i requisiti di precisione, i avranno un totale di 29217 campioni se si estende l'intervallo di a per semplicità.atan(2z)0z<1floor(log2(z))z1atan(2z)z0z<32

Per riferimento futuro, ecco il grosso script di Python che ho usato per calcolare gli errori di approssimazione:

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])    

L'errore massimo locale derivante dall'approssimazione di una funzione mediante l'interpolazione lineare di da campioni di , prelevati mediante campionamento uniforme con intervallo di campionamento , può essere approssimato analiticamente mediante:f(x)f ( x ) f ( x ) Δ xf^(x)f(x)Δx

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

dove è la seconda derivata di e è al massimo locale dell'errore assoluto. Con quanto sopra otteniamo le approssimazioni: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).

Poiché le funzioni sono concava e i campioni corrispondono alla funzione, l'errore è sempre in una direzione. L'errore assoluto massimo locale potrebbe essere dimezzato se il segno dell'errore fosse fatto alternare avanti e indietro una volta ogni intervallo di campionamento. Con l'interpolazione lineare, è possibile raggiungere risultati ottimali prefiltrando ogni tabella mediante:

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,

dove ed sono l'originale e la tabella filtrata sia attraversa ei pesi sono . Il condizionamento finale (prima e ultima riga dell'equazione precedente) riduce l'errore alle estremità della tabella rispetto all'utilizzo di campioni della funzione all'esterno della tabella, poiché non è necessario regolare il primo e l'ultimo campione per ridurre l'errore di interpolazione tra esso e un campione appena fuori dal tavolo. Sottotitoli con intervalli di campionamento diversi devono essere pre-filtrati separatamente. I valori dei pesi sono stati trovati minimizzando in sequenza per aumentare l'esponentexy0kNc0=98,c1=116,b0=1516,b1=18,b2=116c0,c1N il valore assoluto massimo dell'errore approssimativo:

(Δ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

per le posizioni di interpolazione tra campioni , con una funzione concava o convessa (ad esempio ). i pesi, i valori dei pesi di condizionamento finale sono stati trovati minimizzando allo stesso modo il valore assoluto massimo di: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

per . L'uso del prefiltro dimezza l'errore di approssimazione ed è più semplice da eseguire rispetto all'ottimizzazione completa delle tabelle.0a<1

Errore di approssimazione con e senza prefiltro e fine condizionamento

Figura 4. Errore di approssimazione di da 11 campioni, con e senza prefiltro e con e senza condizionamento finale. Senza il condizionamento finale il prefiltro ha accesso ai valori della funzione appena fuori dalla tabella.log2(a)

Questo articolo presenta probabilmente un algoritmo molto simile: R. Gutierrez, V. Torres e J. Valls, " Implementazione FPGA di atan (Y / X) basata sulla trasformazione logaritmica e tecniche basate su LUT " , Journal of Systems Architecture , vol . 56, 2010. L'abstract afferma che la loro implementazione supera i precedenti algoritmi basati su CORDIC in termini di velocità e algoritmi basati su LUT in termini di dimensioni dell'impronta.


3
Matthew Gambrell e io abbiamo progettato al contrario il chip audio Yamaha YM3812 del 1985 (al microscopio) e abbiamo trovato in esso tabelle di memoria (ROM) log / exp simili. Yamaha aveva usato un trucco aggiuntivo per sostituire ogni seconda voce di ogni tabella con una differenza rispetto alla voce precedente. Per funzioni uniformi, la differenza richiede meno bit e area del chip da rappresentare rispetto alla funzione. Avevano già un sommatore sul chip che potevano usare per aggiungere la differenza alla voce precedente.
Olli Niemitalo,

3
Grazie mille! Adoro questo tipo di exploit di proprietà matematiche. Svilupperò sicuramente alcune sim di MATLAB di questo, e se tutto sembra a posto, passerò all'HDL. Riporterò indietro i miei risparmi sui LUT quando tutto sarà fatto.
user2913869,

Ho usato la tua descrizione come guida e sono felice di rimanere che ho ridotto di LUT di quasi il 60%. Avevo bisogno di ridurre i BRAM, quindi ho capito che avrei potuto ottenere un errore massimo coerente sulla mia tabella ATAN eseguendo un campionamento non uniforme: avevo più LAM BRAM (tutti con lo stesso numero di bit di indirizzo), più vicino a zero, più veloce è il campionamento. Ho scelto i miei intervalli di tabelle come potenze di 2 in modo da poter facilmente rilevare in quale intervallo ero anche e fare l'indicizzazione automatica delle tabelle tramite la manipolazione dei bit. Ho applicato anche una simmetria atan, quindi ho memorizzato solo metà della forma d'onda.
user2913869,

Inoltre, potrei aver perso alcune delle tue modifiche, ma sono riuscito a implementare 2 ^ z suddividendolo in 2 ^ {if} = 2 ^ i * 2 ^ {0.f}, dove i è la parte intera e f è la parte frazionaria. 2 ^ i è semplice, solo manipolazione di bit, e 2 ^ {0.f} aveva un intervallo limitato, quindi si prestava bene a LUT con interpolazione. Ho anche gestito il caso negativo: 2 ^ {- if} = 2 ^ {- i} * 1 / (2 ^ {0.f}. Quindi un'altra tabella per 1/2 ^ {0.f}. Il mio prossimo passo potrebbe essere quella di applicare la potenza di 2 che vanno campionamento / non uniforme sui log2 (y) LUT, come che sembra come sarebbe la forma d'onda candidato perfetto per questo genere di cose Saluti.!
user2913869

1
Lol yup, ho perso del tutto questo passaggio. Lo proverò adesso. Dovrebbe salvarmi ancora più LUT e ancora più BRAM
user2913869,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.