FPGA上定点atan2的计算方法


12

我需要atan2(x,y)在具有连续输入/输出数据流的FPGA上进行计算。我设法使用展开的,流水线的CORDIC内核来实现它,但是要获得所需的精度,我必须执行32次迭代。这导致大量的LUT致力于这一任务。我尝试将流更改为使用部分展开的CORDIC内核,但随后我需要一个倍频的时钟频率以执行重复循环,同时仍保持连续的输入/输出流。因此,我无法满足时机要求。

因此,现在我正在寻求替代的计算方式atan2(x,y)

我考虑过使用带插值的Block-RAM查找表,但是由于有2个变量,因此我需要2维查找表,这在Block-RAM的使用方面非常耗费资源。

然后,我考虑使用与象限调整atan2(x,y)有关的事实atan(x/y)。问题在于,x/y由于y不是常数,因此需要真正的划分,而FPGA上的划分非常占用资源。

是否有更多新颖的方法可atan2(x,y)在FPGA 上实现,从而降低LUT使用率,但仍提供良好的准确性?


2
您的处理时钟速率是多少,输入数据速率是多少?
吉姆·克莱

您要求的精度是多少?我还假设您正在使用定点计算。您正在使用什么深度?具有象限调整的多项式逼近(或LUT)是一种常见的实现方法atan2。不过,不确定是否可以不分手而行。
杰森R

输入时钟为150MHz,输入数据速率为150 MSamps / sec。基本上,每个时钟周期我都会得到一个新的输入。有延迟是可以的,但我还必须产生150 MSamps / sec的输出。
user2913869 '02

我的模拟显示我可以忍受大约1 * 10 ^ -9。不确定绝对最小定点位数,但我一直在使用Q10.32定点格式进行仿真
user2913869

本文介绍的定点实现atan2。不过,您仍然需要一个部门。
Matt L.

Answers:


20

您可以使用对数摆脱除法。对于第一象限中的:(x,y)

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

阿丹(2 ^ z)

图1.atan(2z)

您可能需要在范围内近似才能获得所需的1E-9精度。您可以利用对称或确保在已知的八分圆中。近似:atan(2z)30<z<30atan(2z)=π2atan(2z)(x,y)log2(a)

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

b可以通过找到最高有效非零位的位置来计算。可以通过位移来计算。您将需要在范围近似。clog2(c)1c<2

log2(c)

图2.log2(c)

对于你的精度要求,线性内插和均匀采样,的样品和的样品为应该足够了。后者的桌子很大。有了它,由于插值引起的误差在很大程度上取决于:214+1=16385log2(c)30×212+1=122881atan(2z)0<z<30z

atan(2 ^ z)逼近的误差

图3. 对于每单位间隔的不同数量的样本(32至8192),在不同范围(水平轴近似最大绝对误差。(忽略)的最大绝对误差略小于绝对误差。atan(2z)zz0z<1floor(log2(z))=0

所述表可以被分成多个子表对应于和不同与,这是很容易计算。表长度可以选择,如图3所示。子表内索引可以通过简单的位串操作来计算。为了您的准确性要求,如果您将的范围扩展到为简单起见,子表将总共包含29217个样本。atan(2z)0z<1floor(log2(z))z1atan(2z)z0z<32

供以后参考,这是我用来计算近似误差的笨拙的Python脚本:

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

通过对样本进行线性插值来近似函数的局部最大误差,可以通过采样间隔进行均匀采样而得出,可以通过以下方式近似解析:f(x)˚FX ˚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,

其中是的二阶导数,并且处于绝对误差的局部最大值。通过以上方法,我们得到了近似值: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).

由于这些函数是凹的,并且样本与该函数匹配,因此误差始终在一个方向上。如果使误差的符号在每个采样间隔内来回交替一次,则局部最大绝对误差可以减半。使用线性插值,可以通过以下方式对每个表进行预过滤来获得接近最佳结果:

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

带有和不带有预滤波器和末端条件的近似误差

图4. 11个样本的近似误差,带有和不带有预滤波器,带有和不带有端部条件。在没有结束条件的情况下,预过滤器可以访问表外部的函数值。log2(a)

本文可能提出了一种非常相似的算法:R. Gutierrez,V。Torres和J. Valls,“ 基于对数变换和基于LUT的技术的atan(Y / X)的FPGA实现系统结构学报》, 。2010年5月56日。摘要表示,它们的实现在速度方面击败了先前基于CORDIC的算法,在占用空间方面又优于基于LUT的算法。


3
我和Matthew Gambrell对1985 Yamaha YM3812声音芯片(通过显微镜)进行了反向工程,并在其中发现了类似的日志/扩展只读存储器(ROM)表。Yamaha使用了另一个技巧,即将每个表中的第二个条目替换为与前一个条目的不同之处。对于平稳的功能,差异代表的位数和芯片面积少于函数。他们已经在芯片上有一个加法器,可以用来将差异添加到上一个条目。
Olli Niemitalo

3
非常感谢你!我喜欢这些数学性质的研究。我一定会对此进行一些MATLAB模拟,如果一切看起来不错,请转到HDL。完成所有步骤后,我将报告我的LUT节省。
user2913869 '02

我以您的描述为指导,很高兴能将LUT减少了近60%。我确实需要减少BRAM,因此我发现通过进行非均匀采样可以在ATAN表上获得一致的最大错误:我有多个LUT BRAM(地址位均相同),越接近零,采样越快。我将表范围选择为2的幂,因此我可以轻松地检测到自己所在的范围,并通过位操作进行自动表索引。我还应用了atan对称性,因此只存储了一半的波形。
user2913869 '16

另外,我可能已经错过了您的一些修改,但是我设法通过将其拆分为2 ^ {if} = 2 ^ i * 2 ^ {0.f}来实现2 ^ z,其中i是整数部分,f是小数部分。2 ^ i很简单,只需进行位操作,而2 ^ {0.f}的范围有限,因此它很容易通过插值应用到LUT。我还处理了否定情况:2 ^ {-if} = 2 ^ {-i} * 1 /(2 ^ {0.f}。因此,又有一张表用于1/2 ^ {0.f}。下一步可能是在log2(y)LUT上施加2个测距/非均匀采样的幂,因为这似乎将是这种事情的理想候选波形。干杯!
user2913869 2016年

1
大声笑,我完全错过了这一步。我现在要尝试。应该为我节省更多的LUT和更多的BRAM
2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.