一种计算以2为底的对数的方法

计算对数是数字信号处理中相当普遍的操作。 也许更多时候,仅应考虑卷积(与累积相乘)和振幅与相位。 通常,为了在FPGA上计算对数,在双曲线版本中使用了CORDIC算法 ,只需要表格和简单的算术运算即可。 但是,这并不总是很方便,尤其是在项目较大,晶体较小且开始进行优化的情况下。 在这种情况下,我不得不面对一天。 RAM模块的两个端口(Cyclone IV)已经紧密工作,没有空闲的窗口。 我不想对双曲CORDIC使用另一个块。 但是有一个乘法器,为此在时序图中获得了不错的自由窗口。 经过一天的思考,我编写了以下算法,其中不使用表,但是有乘法,更精确地说是平方。 而且由于电路平方比一般的乘法要简单,因此该算法可能是专用芯片感兴趣的,尽管FPGA当然没有区别。 削减更多细节。

解释什么是实数更容易。 让我们从他们开始。 稍后我们将进行整数实现。

设一个数字X。 找出这样的数字Y X=2Y
我们还假定X在1到2的范围内。这不会过多地限制通用性,因为X总是可以通过乘或除以2的幂来转换为该间隔。 对于Y,这意味着加或减一个整数,这很容易。 因此, X位于1到2的区间中。然后Y将位于0到1的区间中。我们将Y表示为无穷二进制分数:

Y=b020+b121+...+bn2n+...


赔率 bi 在此记录中,只不过是数字Y的二进制表示形式的位而已。 此外,由于Y小于1,显然 b0 = 0。

让我们平方第一个方程: X2=22Y 和以前一样,我们编写2Y的二进制表示形式。 显然,

2Y=b120+b221+...+bn2n1+...

即 位 bi 保持不变,只是两个人的力量感动了。 蝙蝠 b0 在视图中不存在,因为它等于零。

有两种情况:

1) X2>2 ,2Y> 1, b1=1

2) X2<2 ,2Y <1, b1=0

在第一种情况下,我们将X作为新值 X2/2 在第二个- X2

结果,任务减少到前者。 新的X仍在1到2的范围内,新的Y在0到1的范围内。但是我们了解到了一点点结果。 在将来采取相同的步骤,我们可以获得尽可能多的Y位。

让我们看看它在C程序中如何工作:

#include <stdio.h> #include <math.h> int main() { double w=1.4; double s=0.0; double a=0.5; double u=w; for(int i=0; i<16; i++) { u=u*u; if(u>2) { u=u/2; s+=a; } a*=0.5; } w=log2(w); double err=100*abs(2*(sw)/(s+w)); printf("res=%f, log=%f, err=%f%c\n",s,w,err,'%'); return 0; } 

我们以16位精度计算对数,并与数学库提供的对数进行比较。 该计划带来了:

res = 0.485413,log = 0.485427,err = 0.002931%

结果与该库吻合,精度为0.003%,说明了我们算法的有效性。

让我们继续一个整数实现。

令N位二进制无符号数字表示间隔[0,1]。 为了方便起见,我们考虑单位编号 2N 但不是 2N1 ,并因此得出一个减数 2 N + 1 。 我们将按照上一个的图像和相似之处编写一个程序,但是要使用整数:

 #include <stdio.h> #include <math.h> #define DIG 18 //  #define N_BITS 16 //    unsigned ONE=1<<(DIG-1); // unsigned TWO=ONE<<1; // unsigned SCALE=1<<(N_BITS+1); //  unsigned myLog(unsigned w) { unsigned s=0; unsigned long long u=w; for(int i=0; i<N_BITS+1; i++) { s<<=1; u=(u*u)>>(DIG-1); //    ! if(u&TWO) //      { u>>=1; s+=1; } printf("%X\n", (int)u); } return s; } int main() { double w=1.2345678; unsigned iw=(unsigned)(ONE*w); double dlog=log2(w); unsigned ilog=myLog(iw); unsigned test=(unsigned)(SCALE*dlog); int err=abs((int)(ilog-test)); printf("val=0x%X, res=0x%X, log=0x%X, err=%d\n",iw,ilog,test,err); return 0; } 

在使用具有不同位深度(DIG),计算精度(N_BITS)和对数参数(w)的程序播放后,我们看到所有内容都正确计算了。 特别是,使用此源中指定的参数,程序将产生:

val = 0x27819,res = 0x9BA5,log = 0x9BA6,err = 1

现在,一切都准备就绪,可以像在C语言中实现myLog函数一样,实现对Verillog模拟器的完全控制。与我们的函数中的变量su可以循环打印并进行比较。 这些变量与Iron实现的对应关系非常透明并且可以理解。 u是一个工作寄存器,在迭代过程中采用新的X值。 s是一个移位寄存器,在其中累加结果。 我们模块的接口如下所示:

 module logarithm( input clk, // input wr, //   input[17:0] din, //   output[nbits-1:0] dout, //   output rdy //  ); parameter nbits=16; //  

输入总线分别采用Cyclone IV中乘法器的宽度的18位。 我们模块上的数字应归一化。 即 高位等于一 在我的项目中,这是自动完成的。 但是,在这种情况下,实施规范化工具对任何人来说都不难。 计算的精度由nbits参数设置,默认情况下等于16。模块每个周期计数一位,并且对于16个周期,它以16位精度计算对数。 如果您需要以相同的精度更快或更精确地以相同的速度运行,我希望没有人会很困难地将模块分为多个设备和流水线。

这是完整的模块和测试代码
 //--------------------- logarithm.v ------------------------------// module logarithm( input clk, // input wr, //   input[17:0] din, //   output[nbits-1:0] dout, //   output rdy //  ); parameter nbits=16; //  reg[4:0] cnt; // reg[17:0] acc; // - reg[nbits-1:0] res; // always @(posedge clk) if(wr) cnt<=nbits+1; else if(cnt != 0) cnt<=cnt-1; wire[35:0] square=acc*acc; //  wire bit=square[35]; //  wire[17:0] next = bit ? square[35:18] : square[34:17]; //  always @(posedge clk) if(wr) acc<=din; else if(cnt != 0) begin acc<=next; #10 $display("%X", acc); end always @(posedge clk) if(wr) res<=0; else if(cnt != 0) begin res[nbits-1:1]<=res[nbits-2:0]; res[0]<=bit; end assign dout=res; assign rdy=(cnt==0); endmodule //======================== testbench.v =====================// module testbench(); reg clk; // always #100 clk=~clk; reg wr; // reg[17:0] din; // wire rdy; // wire[15:0] dout; // logarithm log2( .clk (clk), .wr (wr), .din (din), .dout (dout), .rdy (rdy) ); //  n     task skipClk(integer n); integer i; begin for(i=0; i<n; i=i+1) @(posedge clk); #10 ; end endtask initial begin // $dumpfile("testbench.vcd"); $dumpvars(0, testbench); clk=0; wr=0; din=18'h27819; skipClk(3); wr=1; skipClk(1); wr=0; @(rdy); skipClk(3); $display("value=%X, result=%X", din, dout); $display("Done !"); $finish; end endmodule 


使用以下脚本运行测试:

 #!/bin/sh rm -f *.vvp rm -f *.vcd iverilog -o testbench.vvp logarithm.v testbench.v vvp testbench.vvp gtkwave testbench.vcd testbench.gtkw 

运行测试,我们看到模拟器的最终输出-值= 27819,结果= 9ba5。 Verilog提供了与C相同的功能。此处的时序图非常琐碎,没有特别的意义。 因此,我不带它。

比较模拟器的中间输出(acc)和以C为单位的程序:
Verilog C
30c5d 30C5D
252b1 252B1
2b2bc 2B2BC
3a3dc 3A3DC
35002 35002
2be43 2BE43
3c339 3C339
38a0d 38A0D
321b0 321B0
273a3 273A3
30163 30163
24214 24214
28caf 28CAF
34005 34005
2a408 2A408
37c9d 37C9D
30a15 30A15


确保它们一点一点地匹配。 总的来说,在verilo上的实现最多重复了C模型,这是应该通过在硬件中实现算法来实现的结果。

仅此而已。 我希望有人会觉得我的经验有用。

Source: https://habr.com/ru/post/zh-CN469327/


All Articles