开平方运算(Non-Restoring)
在书《基于FPGA的数字信号处理》中的开平方运算一节中,作者给出了一种基于Non-Restoring的算法,但是没有给出具体推导过程,本文试图给出算法的简单证明。熟悉十位数的开方运算的读者可以跳转到第二部分
十位数的开平方运算
对于十位数的开方运算,我们有许多算法,比如泰勒展开、牛顿迭代法、利用常数0x5f375a86加速收敛的牛顿迭代法等。在书中使用的是Non-Restoring算法,本文就专门讨论这个算法。
对于一个6位的十位数D,它的每一位用dn表示,即
D=d5d4d3d2d1d0D=d_5d_4d_3d_2d_1d_0D=d5d4d3d2d1d0
它的n次根号的位数为 6 / n位,不严谨的简单证明如下:
一个数字a的整数部分长度为n,小数部分长度为无限长,它的平方的取值范围即为[10n,10n+2][10^n,10^{n+2}][10n,10n+2]。
类比除法的运算规则,我们可以利用迭代的减法操作,从高位到低位逐级算出解。
回顾除法运算,例如除法算式 1354/31354/31354/3,我们有如下操作
1513 )1354‾12 ‾15 15 ‾43‾1
\begin{array}{lr}
& 151 \\
3 \!\!\!\!\!\! & \overline{)1354} \\
& \underline{12\ \ \ \ } \\
& 15\ \ \\
& \underline{15\ \ } \\
& 4 \\
& \underline{3}\\
& 1
& \end{array}
3151)135412 15 15 431
为了取得每一位的解,在每一位上,我们都需要找出数字n,n满足两个条件:
- n是正整数且只有一位,对于十进制取值范围为{0,1,2,3,4,5,6,7,8,9}。
- {上一次运算的余项,本次运算的被除数对应位} - n * 除数 的值 为最小值。
我们来看看根号的手解法。
D=d5d4d3d2d1d0D=d_5d_4d_3d_2d_1d_0D=d5d4d3d2d1d0
Q=D=q2q1q0=q2∗102+q1∗10+q0Q=\sqrt{D}=q_2q_1q_0=q_2*10^2+q_1*10+q_0Q=D=q2q1q0=q2∗102+q1∗10+q0
当我们要计算Q时,q2q_2q2的值可以由d5d4d_5d_4d5d4直接平方后试减得到,如598213\sqrt{598213}598213的百位为7。
可以把算出来的数字设置为a(上句话运算结果为a=700),下一位待计算的值为b。我们可以利用(a+b)2−a2=2ab+b2(a+b)^2-a^2=2ab+b^2(a+b)2−a2=2ab+b2试出数字b,b满足两个条件:
- b是正整数,对于十进制,最高位取值范围为{0,1,2,3,4,5,6,7,8,9},其余位为0。
- 2ab+b22ab+b^22ab+b2 - {上一次运算的余项,本次运算的被除数对应位} 的值 为最小值。
如果a、b的值不包含未计算部分,则上述条件将会有些许变化:
- b是正整数且只有一位,对于十进制,取值范围为{0,1,2,3,4,5,6,7,8,9}
- {上一次运算的余项,本次运算的被除数对应位} 20ab+b220ab+b^220ab+b2 的值 为最小值。
一个例子是
7 7 3 59 82 1349 ‾10 82 10 29 ‾53 1346 29‾6 84
\begin{array}{lr}
& 7\ \ \ 7\ \ \ 3\ \\
\!\!\!\!\!\! & \sqrt{59\ 82\ 13}\\
& \underline{49\ \ \ \ \ \ \ \ \ \ } \\
& 10\ 82\ \ \ \ \ \\
& \underline{10\ 29\ \ \ \ \ } \\
& 53\ 13\\
& \underline{46\ 29}\\
& 6\ 84
& \end{array}
7 7 3 59 82 1349 10 82 10 29 53 1346 296 84
795213=7732+684\sqrt{795213} = 773^2+684795213=7732+684
| a | 前次余项+本次运算块 | 20ab+b220ab+b^220ab+b2 | b |
|---|---|---|---|
| 0 | 59 | 49 | 7 |
| 7 | 1082 | 1029 | 7 |
| 77 | 5313 | 4629 | 3 |
| 773 | 684 | - | - |
对于n次根号,我们需要将被n次开方处理的数字每n位分割。将上述迭代减法的计算式换成(a+b)n−an(a+b)^n-a^n(a+b)n−an即可。
fpga上的开平方运算
针对上述算法我们可以进行电路实现。首先面临的就是进制转换问题。二进制数字的平方与十进制数有很多相似之处,比如:
- 一个定点数二进制数字a的整数部分(无符号位)长度为n,小数部分长度为无限长,它的平方的取值范围即为[2n,2n+2][2^n,2^{n+2}][2n,2n+2]
- 它的n次根号的位数为 6 / n位
- 迭代计算中用到的式子同样是(a+b)2−a2(a+b)^2-a^2(a+b)2−a2(a、b包括未计算部分),第二种形式为4ab+b24ab+b^24ab+b2(a、b不包括未计算部分)
不过,二进制数有一些有利的性质,我们可以利用他们来简化大量计算,如迭代用的式子4ab+b24ab+b^24ab+b2,用二进制表示可以优化为(100)bab+(b)2=(ab 0b)b(100)_bab+(b)^2=(ab\ 0b)_b(100)bab+(b)2=(ab 0b)b 其中a为一串二进制数,b为一位二进制数。
当迭代的减法式子写作(ab 0b)b(ab\ 0b)_b(ab 0b)b时:
1 1 0 1 10 10 10 0101 ‾01 10 1 01 ‾01 10 00 00 ‾1 10 011 10 01‾0
\begin{array}{lr}
& 1\ \ \ 1\ \ \ 0\ \ \ 1\\
\!\!\!\!\!\! & \sqrt{10\ 10\ 10\ 01}\\
& \underline{01\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ } \\
& 01 \ 10\ \ \ \ \ \ \ \ \ \ \\
& \underline{1\ 01\ \ \ \ \ \ \ \ \ \ } \\
& 01\ 10\ \ \ \ \ \\
& \underline{00\ 00\ \ \ \ \ }\\
& 1\ 10\ 01\\
& \underline{1\ 10\ 01 }\\
&0
& \end{array}
1 1 0 110 10 10 0101 01 10 1 01 01 10 00 00 1 10 011 10 010
(10101001)b=(1101)b2\sqrt{(10101001)_b} = (1101)_b^2(10101001)b=(1101)b2
| a | 前次余项+本次运算块 | (ab 0b)b(ab\ 0b)_b(ab 0b)b | b |
|---|---|---|---|
| 0 | 010 | 001 | 1 |
| 1 | 0110 | 101 | 1 |
| 11 | 0110 | 0000 | 0 |
| 110 | 11001 | 11001 | 1 |
| 1101 | 0 | - | - |
此计算过程可以用恢复余数算法表示。

当相减后b<0时,需要恢复相减之前的值。



Restoring 算法很直观,但存在的问题是:若当前部分余数为负时,需要额外的加法操作使其恢复到前一次迭代时产生的部分余数。这不仅需要额外的资源,还增加了Latency。是否可以不恢复余数呢?这需要对算法进行一定的修改。
由式(3.62)可知,式(3.63)可重写为r0=r1D(1)D(0)+q20100−q101r_0=r_1D(1)D(0)+q_20100-q_101r0=r1D(1)D(0)+q20100−q101



书中的开平方运算
在《基于FPGA的数字信号处理》一书中,用到的算法(Non Restoring)如下:

简单分析可知,书中算法比我们的算法大致相同,不过为了保持运算的一致性,将减法操作统一为加法。图片中有一个错误,即0S3S2110S_3S_2110S3S211应该为0S3S2S1110S_3S_2S_1110S3S2S111
看懂这个算法需要两个小知识点:
- A - B = A + ( -B) ,等于A的补码加(-B)的补码
- if (A >= B) A+补(B)将会产生进位
else if(A < B) A+补(B)将不会产生进位
我们可以根据 补(余数:当前项)+迭代式补({余数:当前项})+迭代式补(余数:当前项)+迭代式 是否产生进位来决定下一步迭代的迭代式选取。Q(k+1)代表前一次运算的进位情况,补码情况下
由前文可知,
rk={rk+1D(2k+1)D(2k)−qk+101if Q(k+1)==1 rk+1D(2k+1)D(2k)+qk+111(if Q(k+1)==0r_k=\begin{cases}
r_{k+1}D(2k+1)D(2k) -q_{k+1}01 &\text{if Q(k+1)==1 }\\
r_{k+1}D(2k+1)D(2k) +q_{k+1}11 ( &\text{if Q(k+1)==0}
\end{cases}rk={rk+1D(2k+1)D(2k)−qk+101rk+1D(2k+1)D(2k)+qk+111(if Q(k+1)==1 if Q(k+1)==0
如果使用补码运算,式子将会修改如下+
rk={rk+1D(2k+1)D(2k)+0qk+111if Q(k+1)==0rk+1D(2k+1)D(2k)+1q~k+111(if Q(k+1)==1r_k=\begin{cases}
r_{k+1}D(2k+1)D(2k) +0q_{k+1}11 &\text{if Q(k+1)==0}\\
r_{k+1}D(2k+1)D(2k) +1\widetilde{q}_{k+1}11 ( &\text{if Q(k+1)==1}
\end{cases}rk={rk+1D(2k+1)D(2k)+0qk+111rk+1D(2k+1)D(2k)+1qk+111(if Q(k+1)==0if Q(k+1)==1
| qkq_kqk | rk+1D(2k+1)D(2k)r_{k+1}D(2k+1)D(2k)rk+1D(2k+1)D(2k) | Q(k+1) ? 0qk+111 : 0qk+111Q(k+1)\ ?\ 0q_{k+1}11\ :\ 0q_{k+1}11Q(k+1) ? 0qk+111 : 0qk+111 | 进位情况 |
|---|---|---|---|
| - | 010 | 111 | 1 |
| 1 | 0110 | 1S~311=10111\widetilde{S}_311=10111S311=1011 | 1 |
| 11 | 00110 | 1S~3S~211=100111\widetilde{S}_3\widetilde{S}_211=100111S3S211=10011 | 0 |
| 110 | 100101 | 0S3S2S111=0110110S_3S_2S_111=0110110S3S2S111=011011 | 1 |
| 1101 | 0 | - | - |
本文详细介绍了非恢复性(Non-Restoring)开平方运算的原理,从十进制的算法出发,通过类比除法运算规则,阐述了如何逐位计算平方根。接着,将算法应用于FPGA(Field-Programmable Gate Array)数字信号处理中,探讨了二进制下的迭代计算和进制转换,并分析了如何通过调整算法减少资源消耗和延迟。最后,对比了恢复余数算法,指出非恢复性算法的优势在于避免了额外的加法操作。


被折叠的 条评论
为什么被折叠?



