如何在 FPGA 上对 sqrt(x) 执行小值逼近

电器工程 FPGA
2022-01-21 07:55:02

我正在尝试实现一个涉及计算值的定点例程x对于小x那接近0. 目标架构是FPGA。一个问题是这个函数本身并不适合泰勒展开式的使用。可以看出,对于较小的 x 值,xx方法0,因此使用幂级数评估函数涉及将大系数与小系数相乘x. 因此,这种方法在数值上是不稳定的。

使用迭代方法,Newton-Raphson 产生以下迭代方程:xn+1=xn2α2xn, 我们试图近似的地方α. 但再一次,因为α是小,xn同样必须很小才能使解决方案收敛。由于该等式涉及将一个小数除以另一个小数,因此定点算术很可能会失败。

有了这个,我想知道如何实现小值近似x使用定点算术,使用预先计算的系数或迭代方法。

4个回答

我以前使用过的例程(我不知道它是否是“适当的”例程)是一种分而治之的方法。

您从一个任意的上限值和下限值开始(分别为 5 和 0 - 您想要找到的最高和最低平方根)并找到它们之间的中点。平方该值。

如果平方值大于您的目标,请将上限值设置为您的平方值。如果它较低,则设置较低的值。

重复直到平方值与您的查找值匹配,或者您已经执行了足够的迭代以达到您想要的准确度。

这是我在 perl 中拼凑的一个小版本:

#!/usr/bin/perl

my $val = shift;

my $max = 5;
my $min = 0;

my $iterations = 0;
my $maxiter = 40;

while(($max > $min) and ($iterations<$maxiter))
{
    $iterations++;
    my $diff = $min + ($max - $min) / 2;
    my $square = $diff * $diff;

    if($square == $val)
    {

        print "Square root found at $diff\n";
        print "$iterations iterations\n";
        exit(0);
    } else {
        if($square > $val)
        {
            $max = $diff;
        } else {
            $min = $diff;
        }
    }
}

my $diff = $min + ($max - $min) / 2;
print "Approximate square root after $iterations iterations: $diff\n";

这当然是使用浮点,但可以很容易地适应定点。您可以通过更改迭代限制来改变精度。每次迭代都比前一次更准确。

例如: - 求 9 的平方根:

Approximate square root after 40 iterations: 2.99999999999955
   - or - 
Approximate square root after 10 iterations: 3.00048828125
   - or - 
Approximate square root after 5 iterations: 3.046875

如果它找到了值 3,它当然会提前停止。

给它足够的迭代,它应该得到非常准确:

./sqrt.pl 0.00284
Square root found at 0.0532916503778969
59 iterations

这里有一些来自提升超然大师 / Guru Scott Dattalo 的想法和惯例
除了大师(Guru?)部分之外,这当然是一个笑话。斯科特很棒。

相关讨论。 2005 和 PIC,有些是 C,但可能有价值。

斯科特再次 - 2003

两位大师!!!
达塔洛和戈洛夫琴科。
一系列方法

您没有指定“小值”或“近似值”的含义。所以我要提出的建议可能行不通,但就这样吧。

最简单的事情是制作一个查找表。本质上是一个 ROM,其中地址总线是您想要平方根的数字,数据输出是结果。使用单个 BRAM,您可以执行 9 位输入、8 位输出 LUT。当然,更多的 BRAM 会给你一张更大的桌子。

(BRAM = 块 RAM 的 Xilinx 术语,也可以用作 ROM。其他 FPGA 也有类似的东西。)

如果您想要比 BRAM 更高的精度,您可以对两个 LUT 条目进行简单的线性插值。例如,假设您想要一个 12 位输入,但您只有 10 位的 BRAM。您获取输入的前 10 位并在 LUT 中查找。将这 10 位加 1 并查找该值。然后,您在两个结果之间进行简单的线性插值,使用底部 2 位来告诉您一个值与另一个值的比例。当然,这只会给您一个近似值,但我认为如果您进行数学运算,您会发现它可能已经足够好了。

这种方法对低值数字最不准确,但随着输入值变高,准确度会上升。

上述方法的优化是将 BRAM 用作双端口 ROM。通过这种方式,您可以在不增加使用的 BRAM 数量的情况下读出两个值。这也将允许您计算每个时钟周期的 SQRT,并带有一些流水线延迟。

顺便说一句,这种方法也适用于正弦/余弦!

尝试以下方法

  • 如果数字为负数,请相应处理。
  • 如果数字为 0,则返回 0。
  • 否则:
  • 归一化为 [1/4, 1] 范围内的数字:计算你必须将数字乘以 4 的次数(x <<= 2在 C 中),直到它在上述范围内。
  • 使用任意方法(多项式逼近、牛顿法的 sqrt a[n] = (a[n-1]+k/a[n-1])/2 等)计算此范围内的平方根
  • 非规范化:右移 k 位