参考资料:
https://blog.csdn.net/qq_39210023/article/details/77456031
https://blog.csdn.net/messi_cyc/article/details/77966457
https://wenku.baidu.com/view/6c623aa8910ef12d2bf9e732.html?sxts=1521614036184(Xilinx CORDIC,值得参考)
http://blog.sina.com.cn/s/blog_b906c1070102ve1l.html (本篇的C语言程序很有参考价值)
CORDIC算法涉及3种坐标系、2种模式,共计6这个组合,其中在圆周坐标系下利用旋转模式实现sin和cos的文章和资料较多,其他5种较少,利用FPGA实现的更少。本文实现圆周坐标系下的向量模式,对输入的一对(x,y),求得actan(y/x)和 sqrt(x^2+y^2) ,具体原理以上资料均已很清楚,如有不清楚的可在评论区提出,文中不再赘述。文章侧重于Verilog程序的设计。
(1)输入、输出位宽32,设为signed有符号数,这样便于下面的操作,通过判断y的最高位y[31]为1还是为0即可判断正负,对于移位操作也可以直接运用“>>>”,而不需要进行位拼接操作
//输入
input clk;
input rst_n;
input signed [31:0] x;
input signed [31:0] y;
//输出
output reg signed [31:0] sqrt;
output reg signed [31:0] actan;
(2)定义旋转角度常量和中间寄存器,此处借鉴了参考资料1中的定义(几乎是照抄,特别感谢@善良的一休君,文章给了我很多启发)
//以下为了避免浮点运算,对每个变量θi都放大了2^16倍
`define rot0 32'd2949120 //45度*2^16
`define rot1 32'd1740992 //26.5651度*2^16
`define rot2 32'd919872 //14.0362度*2^16
`define rot3 32'd466944 //7.1250度*2^16
`define rot4 32'd234368 //3.5763度*2^16
`define rot5 32'd117312 //1.7899度*2^16
`define rot6 32'd58688 //0.8952度*2^16
`define rot7 32'd29312 //0.4476度*2^16
`define rot8 32'd14656 //0.2238度*2^16
`define rot9 32'd7360 //0.1119度*2^16
`define rot10 32'd3648 //0.0560度*2^16
`define rot11 32'd1856 //0.0280度*2^16
`define rot12 32'd896 //0.0140度*2^16
`define rot13 32'd448 //0.0070度*2^16
`define rot14 32'd256 //0.0035度*2^16
`define rot15 32'd128 //0.0018度*2^16
//全部定义为有符号型数据
reg signed [31:0] x0=0,y0=0,z0=0;
reg signed [31:0] x1=0,y1=0,z1=0;
reg signed [31:0] x2=0,y2=0,z2=0;
reg signed [31:0] x3=0,y3=0,z3=0;
reg signed [31:0] x4=0,y4=0,z4=0;
reg signed [31:0] x5=0,y5=0,z5=0;
reg signed [31:0] x6=0,y6=0,z6=0;
reg signed [31:0] x7=0,y7=0,z7=0;
reg signed [31:0] x8=0,y8=0,z8=0;
reg signed [31:0] x9=0,y9=0,z9=0;
reg signed [31:0] x10=0,y10=0,z10=0;
reg signed [31:0] x11=0,y11=0,z11=0;
reg signed [31:0] x12=0,y12=0,z12=0;
reg signed [31:0] x13=0,y13=0,z13=0;
reg signed [31:0] x14=0,y14=0,z14=0;
reg signed [31:0] x15=0,y15=0,z15=0;
reg signed [31:0] x16=0,y16=0,z16=0;
(3)数据预处理,将输入数据x,y赋值给对应的x0,y0,而z0初值设为0,进行16级迭代,最后可得到z16=z0+actan(y/x)=actan(y/x)
//数据预处理
always @ (posedge clk or negedge rst_n)
begin
if( !rst_n ) begin
x0 <= 1'b0;
y0 <= 1'b0;
z0 <= 1'b0;
end
else begin
x0 <= x; //x0赋初值
y0 <= y; //y0赋初值0
z0 <= 32'd0; //z0赋初值0
end
end
//第一级迭代
always @ (posedge clk or negedge rst_n)
begin
if( !rst_n ) begin
x1 <= 1'b0;
y1 <= 1'b0;
z1 <= 1'b0;
end
else if( !y0[31] ) begin //有符号数,最高位为符号位,是1表示<0
x1 <= x0 + y0;
y1 <= y0 - x0;
z1 <= z0 + `rot0;
end
else begin //有符号数,最高位为符号位,是0表示>0
x1 <= x0 - y0;
y1 <= y0 + x0;
z1 <= z0 - `rot0;
end
end
......依次迭代
(4)将所得值赋给输出信号sqrt、actan,此处应该注意x16=K*sqrt(x^2+y^2),输出复数模值时需要除以K或乘以1/K,直接使用除法或乘法器都是非常消耗硬件资源的,因此采用移位的方式除以K(K为常数,具体得出的方式见上面的参考文献),因为输入的数据为了避免浮点运算,均扩大了2^16倍,所以输出的数据也扩大了2^16倍,右移16位的得到输出(但是应注意,这样得到的输出是整数,而无法得到小数的输出,只是为了便于特定数据仿真结果的验证,实际输出时不能采用这种移位方式)
always @ (posedge clk or negedge rst_n)
begin
if( !rst_n ) begin
sqrt <= 1'b0;
actan <= 1'b0;
end
else begin
ppp <= (x16 >>> 1) + (x16 >>> 3) - (x16 >>> 6) - (x16 >>> 9); //相当于x16/0.607523
sqrt <= ppp >>> 16;
aaa <= z16;
actan <= aaa >>> 16;
// sqrt <= x16;
end
end
输入x=y=(2√2)*2^16=185364,预计输出sqrt=4,actan=45 (sqrt(8+8) = 4,tan45 = 1)