métodos de computação do ponto fixo atan2 no FPGA


12

Preciso de computação atan2(x,y)em um FPGA com um fluxo contínuo de dados de entrada / saída. Consegui implementá-lo usando kernels CORDIC desenrolados e em pipeline, mas, para obter a precisão necessária, tive que executar 32 iterações. Isso levou a uma quantidade bastante grande de LUTs dedicadas a essa tarefa. Tentei alterar o fluxo para usar kernels CORDIC parcialmente desenrolados, mas então precisei de uma freqüência de relógio multiplicada para executar loops repetidos, mantendo um fluxo contínuo de entrada / saída. Com isso, eu não consegui encontrar o tempo.

Então agora estou buscando formas alternativas de computação atan2(x,y).

Pensei em usar tabelas de pesquisa de RAM de bloco com interpolação, mas como existem 2 variáveis, eu precisaria de 2 dimensões de tabelas de pesquisa, e isso consome muitos recursos em termos de uso de RAM de bloco.

Pensei então em usar o fato atan2(x,y)relacionado ao atan(x/y)ajuste de quadrante. O problema disso é que ele x/yprecisa de uma verdadeira divisão, já que ynão é uma constante, e as divisões nos FPGAs consomem muitos recursos.

Existem novas maneiras de implementar atan2(x,y)em um FPGA que resultariam em menor uso de LUT, mas ainda assim fornecessem boa precisão?


2
Qual é a sua taxa de clock de processamento e sua taxa de dados de entrada?
Jim Clay

Qual é a precisão que você precisa? Suponho também que você esteja usando computação de ponto fixo. Qual profundidade de bit você está usando? Uma aproximação polinomial (ou LUT) com ajuste de quadrante é um método comum de implementar atan2. Não tenho certeza se você pode sobreviver sem uma divisão, no entanto.
Jason R

O relógio de entrada é de 150MHz, a taxa de dados de entrada é de 150 MSamps / seg. Basicamente, recebo uma nova entrada a cada ciclo do relógio. Ter latência é bom, mas devo produzir uma saída a 150 MSamps / s também.
User2913869

Minhas simulações mostram que eu posso viver com cerca de 1 * 10 ^ -9. Não tenho certeza o mínimo absoluto fixo pedaços de ponto, mas eu fui simulando com um formato de ponto fixo Q10.32
user2913869

Este artigo explica uma implementação de ponto fixo de atan2. Você ainda precisará de uma divisão.
Matt L.

Respostas:


20

Você pode usar logaritmos para se livrar da divisão. Para (x,y) no primeiro quadrante:

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

atan(2^z)

Figura 1. Gráfico de atan(2z)

Você precisaria aproximar atan(2z) no intervalo 30<z<30 para obter a precisão necessária de 1E-9. Você pode tirar proveito da simetria atan(2z)=π2atan(2z)ou, alternativamente, garantir que(x,y)esteja em um octante conhecido. Para aproximar olog2(a) :

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

b pode ser calculado encontrando a localização do bit diferente de zero mais significativo. c pode ser calculado com uma mudança de bit. Você precisaria aproximar olog2(c) no intervalo1c<2 .

log2(c)

Figura 2. Gráfico do log2(c)

Para seus requisitos de precisão, interpolação linear e amostragem uniforme, 214+1=16385 amostras do log2(c) e 30×212+1=122881 amostras de atan(2z) para 0<z<30 devem ser suficientes. A última tabela é bem grande. Com isso, o erro devido à interpolação depende muito de z :

Error of atan(2^z) approximation

Figura 3. atan(2z) aproxima o maior erro absoluto para diferentes faixas de z (eixo horizontal) para diferentes números de amostras (32 a 8192) por unidade de intervalo de z . O maior erro absoluto para 0z<1 (omitido) é ligeiramente menor que para o floor(log2(z))=0 .

A tabela atan(2z) pode ser dividida em várias subtabelas que correspondem a 0z<1 e piso diferente ( log 2 ( z ) )floor(log2(z)) com z1 , o que é fácil de calcular. Os comprimentos da tabela podem ser escolhidos como guiado pela Fig. 3. O índice dentro da subtabela pode ser calculado por uma simples manipulação de cadeia de bits. Para seus requisitos de precisão, as subtabelas atan(2z) terão um total de 29217 amostras se você estender o intervalo de z para 0z<32 por simplicidade.

Para referência posterior, aqui está o script Python desajeitado que eu usei para calcular os erros de aproximação:

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

O erro máximo local de aproximação de uma função f(x) por interpolação linear f ( x ) a partir de amostras de f ( x ) , feita por amostragem uniforme com intervalo de amostragem Δ x , pode ser aproximada analiticamente por: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,

onde f(x) é a segunda derivada de f(x) e x está no máximo local do erro absoluto. Com o exposto, obtemos as aproximações:

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

Como as funções são côncavas e as amostras correspondem à função, o erro ocorre sempre em uma direção. O erro absoluto máximo local pode ser reduzido pela metade se o sinal do erro for alternado para frente e para trás uma vez a cada intervalo de amostragem. Com interpolação linear, é possível obter resultados próximos ao ideal pré-filtrando cada tabela com:

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,

where x and y are the original and the filtered table both spanning 0kN and the weights are c0=98,c1=116,b0=1516,b1=18,b2=116. The end conditioning (first and last row in the above equation) reduces error at the ends of the table compared to using samples of the function outside of the table, because the first and the last sample need not be adjusted to reduce the error from interpolation between it and a sample just outside the table. Subtables with different sampling intervals should be prefiltered separately. The values of the weights c0,c1 were found by minimizing sequentially for increasing exponent N the maximum absolute value of the approximate error:

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

for inter-sample interpolation positions 0a<1, with a concave or convex function f(x) (for example f(x)=ex). With those weights solved, the values of the end conditioning weights b0,b1,b2 were found by minimizing similarly the maximum absolute value of:

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

for 0a<1. Use of the prefilter about halves the approximation error and is easier to do than full optimization of the tables.

Approximation error with and without prefilter and end conditioning

Figure 4. Approximation error of log2(a) from 11 samples, with and without prefilter and with and without end conditioning. Without end conditioning the prefilter has access to values of the function just outside of the table.

This article probably presents a very similar algorithm: R. Gutierrez, V. Torres, and J. Valls, “FPGA-implementation of atan(Y/X) based on logarithmic transformation and LUT-based techniques,Journal of Systems Architecture, vol. 56, 2010. The abstract says their implementation beats previous CORDIC-based algorithms in speed and LUT-based algorithms in footprint size.


3
Matthew Gambrell and I have reverse engineered the 1985 Yamaha YM3812 sound chip (by microscopy) and found in it similar log/exp read only memory (ROM) tables. Yamaha had used an additional trick of replacing every second entry in each table by a difference to the previous entry. For smooth functions, difference takes less bits and chip area to represent than the function. They already had an adder on the chip that they were able to use to add the difference to the previous entry.
Olli Niemitalo

3
Thank you very much! I love these kinds of exploits of mathematical properties. I will definitely develop some MATLAB sims of this, and if all looks well, move onto HDL. I will report back my LUTs savings when all is done.
user2913869

I used your description as a guide and I am happy to stay that I reduced by LUTs by almost 60%. I did have a need to reduce the BRAMs, so I figured out I could get a consistent max error on my ATAN table by doing non-uniform sampling: I had multiple LUT BRAMs (all the same number of address bits), the closer to zero, the faster the sampling. I chose my table ranges to be powers of 2 so I could easily detect which range I was in as well and do automatic table indexing via bit manipulation. I applied atan symmetry as well so I only stored half the waveform.
user2913869

Also, I may have missed some of your edits, but I managed to implement 2^z by splitting it into 2^{i.f} = 2^i * 2^{0.f}, where i is the integer portion and f is the fractional portion. 2^i is simple, just bit manipulation, and 2^{0.f} had a limited range, so it lent itself well to LUT with interpolation. I also handled the negative case: 2^{-i.f} = 2^{-i} * 1/(2^{0.f}. So one more table for 1/2^{0.f}. My next step might be to apply the power of 2 ranging/non-uniform sampling on the log2(y) LUTs, as that seems like it would be the perfect candidate waveform for that kind of thing. Cheers!
user2913869

1
Lol yup I totally missed that step. I'm going to try that now. Should save me even more LUTs and even more BRAMs
user2913869
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.