逐渐把网上的模板代码完善成现在的代码,期间碰到了各种问题,很多时候都是模板代码有问题,真的是很多坑。
一、需求
编写Verilog代码,替换掉工程里的cordic ip核(arctan功能),输入时钟80MHz,输入使能时钟80/6MHz,输入输出数据X、Y宽度均为16位,输出相位角宽度为16位。最终编写的模块和ip核实现的结果进行对比。
一、代码解释
代码中添加了vivado的cordic ip核进行结果的比对,其中ip核的输入输出宽度均为16位,精度为32位。代码文件包括了输入数据的象限初始化、旋转过程、输出结果。
cordic ip核的设置如下所示
Cordic_arctan 模块文件如下所示
`timescale 1ns / 1ps
//
// Company:
// Engineer: ValentineHP
//
// Create Date: 2025/02/10 22:14:23
// Design Name:
// Module Name: Cordic_arctan
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module Cordic_arctan #(
parameter WIDTH = 16,
parameter ITERS = 16,
parameter PRECISION = 32
)(
input clk,
input rst_n,
//输入数据的请求使能信号
input cordic_req,
//输出数据的使能有效信号
output cordic_ack,
input signed[WIDTH-1:0] X,
input signed[WIDTH-1:0] Y,
output[WIDTH-1:0] amplitude, //幅度,偏大1.64倍,这里做了近似处理
output signed[WIDTH-1:0] theta,
//cordic ip核的输出使能和输出角度信号
output wire done_ip,
output wire signed [WIDTH-1:0] theta_ip
);
/* 这里注释掉是因为 需要要求输出角度值是16位,
又因为cordic ip核的输出角度是弧度表示的*/
// 角度值放大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
/*因此查找表修改成弧度值表示,从cordic的ip核输出theta_ip数据结构fix16_13,
最高位表示符号位,theta_ip[14:13]表示整数位,theta_ip[12:0]表示小数位
可以看出结果是theta_ip放大2^13倍,因此如下表示*/
//角度的弧度值放大2^13次方
`define rot0 16'd6433 //45度*2^13
`define rot1 16'd3798 //26.5651度*2^13
`define rot2 16'd2006 //14.0362度*2^13
`define rot3 16'd1018 //7.1250度*2^13
`define rot4 16'd511 //3.5763度*2^13
`define rot5 16'd255 //1.7899度*2^13
`define rot6 16'd128 //0.8952度*2^13
`define rot7 16'd64 //0.4476度*2^13
`define rot8 16'd32 //0.2238度*2^13
`define rot9 16'd16 //0.1119度*2^13
`define rot10 16'd8 //0.0560度*2^13
`define rot11 16'd4 //0.0280度*2^13
`define rot12 16'd2 //0.0140度*2^13
`define rot13 16'd1 //0.0070度*2^13
`define rot14 16'd0 //0.0035度*2^13
`define rot15 16'd0 //0.0018度*2^13
//定义输入数据的寄存器Xn、Yn、Zn
reg signed[31:0] Xn[16:0];
reg signed[31:0] Yn[16:0];
reg signed[31:0] Zn[16:0];
reg[15:0] rot[15:0];
reg cal_delay[16:0];
assign cordic_ack = cal_delay[ITERS];
assign theta = Zn[ITERS];
//幅度,偏大1.64倍,这里做了近似处理 ,然后缩小了2^15
assign amplitude = ((Xn[ITERS] >>> 1) + (Xn[ITERS] >>> 3) +(Xn[ITERS] >>> 4)) >>> (ITERS-1);
//实例化cordic_ip
cordic_0 cordic_atan_inst(
.aclk(clk),
.aresetn(rst_n),
.s_axis_cartesian_tvalid(cordic_req),
.s_axis_cartesian_tdata({Y,X}),
.m_axis_dout_tvalid(done_ip),
.m_axis_dout_tdata(theta_ip)
);
always@(posedge clk)
begin
rot[0] <= `rot0;
rot[1] <= `rot1;
rot[2] <= `rot2;
rot[3] <= `rot3;
rot[4] <= `rot4;
rot[5] <= `rot5;
rot[6] <= `rot6;
rot[7] <= `rot7;
rot[8] <= `rot8;
rot[9] <= `rot9;
rot[10] <= `rot10;
rot[11] <= `rot11;
rot[12] <= `rot12;
rot[13] <= `rot13;
rot[14] <= `rot14;
rot[15] <= `rot15;
end
always@(posedge clk or negedge rst_n)
begin
if( rst_n == 1'b0)
cal_delay[0] <= 1'b0;
else
cal_delay[0] <= cordic_req;
end
//这里输出使能的思路是从输入请求开始因为迭代计算延迟了16个时钟周期
genvar j;
generate
for(j = 1 ;j < ITERS + 1 ; j = j + 1)
begin: loop
always@(posedge clk or negedge rst_n)
begin
if( rst_n == 1'b0)
cal_delay[j] <= 1'b0;
else
cal_delay[j] <= cal_delay[j-1];
end
end
endgenerate
//将坐标挪到第一和四项限中
always@(posedge clk or negedge rst_n)
begin
if( rst_n == 1'b0)
begin
Xn[0] <= 'd0;
Yn[0] <= 'd0;
Zn[0] <= 'd0;
end
else if( cordic_req == 1'b1)
begin
if( X < $signed(0) && Y < $signed(0))
begin
Xn[0] <= -(X << 15);
Yn[0] <= -(Y << 15);
//这是-180度的弧度值表示
Zn[0] <= -16'd25732;
end
else if( X < $signed(0) && Y >= $signed(0))
/*如果输入数据X,Y很小的话,在旋转迭代更新Xn[i]、Yn[i]会有很大的误差,
因此需要对输入数据进行放大2^15倍处理(网上很多都没有这一步),
如果2^16倍后续计算会溢出*/
begin
Xn[0] <= -(X << 15);
Yn[0] <= -(Y << 15);
//这是180度的弧度值表示
Zn[0] <= 16'd25732;
end
else
begin
Xn[0] <= X << 15;
Yn[0] <= Y << 15;
Zn[0] <= 'd0;
end
end
else
begin
Xn[0] <= Xn[0];
Yn[0] <= Yn[0];
Zn[0] <= Zn[0];
end
end
//旋转迭代更新
genvar i;
generate
for( i = 1 ;i < 17 ;i = i+1)
begin: loop2
always@(posedge clk or negedge rst_n)
begin
if( rst_n == 1'b0)
begin
Xn[i] <= 'd0;
Yn[i] <= 'd0;
Zn[i] <= 'd0;
end
else if( cal_delay[i-1] == 1'b1)
begin
if( Yn[i-1][31] == 1'b0)
begin
Xn[i] <= Xn[i-1] + (Yn[i-1] >>> (i-1));
Yn[i] <= Yn[i-1] - (Xn[i-1] >>> (i-1));
Zn[i] <= Zn[i-1] + rot[i-1];
end
else
begin
Xn[i] <= Xn[i-1] - (Yn[i-1] >>> (i-1));
Yn[i] <= Yn[i-1] + (Xn[i-1] >>> (i-1));
Zn[i] <= Zn[i-1] - rot[i-1];
end
end
else
begin
Xn[i] <= Xn[i];
Yn[i] <= Yn[i];
Zn[i] <= Zn[i];
end
end
end
endgenerate
endmodule
简单的测试文件tb_Cordic_arctan模块如下所示:
`timescale 1ns / 1ps
module tb_Cordic_arctan();
reg clk;
reg rst_n;
reg cordic_req;
wire cordic_ack;
reg signed [15:0] X;
reg signed [15:0] Y;
wire [15:0] amplitude;
wire signed [15:0] theta;
wire done_ip;
wire signed [15:0] theta_ip;
// 实例化被测模块
Cordic_arctan uut (
.clk(clk),
.rst_n(rst_n),
.cordic_req(cordic_req),
.cordic_ack(cordic_ack),
.X(X),
.Y(Y),
.amplitude(amplitude),
.theta(theta),
.done_ip(done_ip),
.theta_ip(theta_ip)
);
// 生成时钟信号(100MHz)
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 测试逻辑
initial begin
// 初始化信号
clk = 0;
rst_n = 1;
cordic_req = 0;
#10 rst_n = 0;
#10 rst_n = 1;
// 测试用例1:验证象限转换 0度
#10 cordic_req = 1;
X = 10;Y = 0;
#10 cordic_req = 0;
// 测试用例2:验证象限转换 45度
#40 X = 16'd10;Y = 16'd10;
#10 cordic_req = 1;
#10 cordic_req = 0;
// 测试用例3:验证象限转换 90度
#40 X = 16'd0;Y = 16'd10;
#10 cordic_req = 1;
#10 cordic_req = 0;
// 测试用例4:验证象限转换 135度
#40 X = -16'd10;Y = 16'd10;
#10 cordic_req = 1;
#10 cordic_req = 0;
// 测试用例5:验证象限转换 180度
#40 X = -16'd10;Y = 16'd0;
#10 cordic_req = 1;
#10 cordic_req = 0;
// 测试用例6:验证象限转换 -135度
#40 X = -16'd10;Y = -135d10;
#10 cordic_req = 1;
#10 cordic_req = 0;
// 测试用例7:验证象限转换 -90度
#40 X = 16'd0;Y = -16'd10;
#10 cordic_req = 1;
#10 cordic_req = 0;
// 测试用例8:验证象限转换 -45度
#40 X = 16'd10;Y = -16'd10;
#10 cordic_req = 1;
#10 cordic_req = 0;
$finish;
end
endmodule
二、运行结果
最终的结果cordic_ack和done_ip需要除以2^13次方得到角度的弧度值,对比两者,输出正确。
三、补充实验细节
由于简单测试文件只是取每个象限的两个值进行测试,不能有效地检测每一个角度值的正确性。因此需要补充DDS模块,生成正弦信号、余弦信号作为Cordic_arctan的输入数据Y、X,做覆盖性测试。这一步很有必要,发现了对输入数据进行放大2^16倍后续计算会溢出的问题,如图所示。
因此添加dds_ip模块
module dds_ip(
input sys_clk,
input sys_rst_n,
output signed[15:0]sin_data,
output signed[15:0]cos_data,
output [15:0]phase_data,
output data_valid,
output phase_valid
);
wire [31:0]data;
assign cos_data={data[15],data[14:0]};
assign sin_data={data[31],data[30:16]};
dds_compiler_0 dds_0(
.aclk(sys_clk), // input wire aclk
.aresetn(sys_rst_n), // 添加复位信号
.m_axis_data_tvalid(data_valid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(data), // output wire [31 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(phase_valid), // output wire m_axis_phase_tvalid
.m_axis_phase_tdata(phase_data) // output wire [15 : 0] m_axis_phase_tdata
);
endmodule
dds ip核相应的设置如下
重新编写顶层文件top模块
module top(
input sys_clk,
input sys_rst_n,
input inrdy,
output done,
output signed[15:0] theta,
output done_ip,
output signed[15:0] theta_ip,
output reg[15:0] delta
);
wire signed[15:0] sin_data;
wire signed[15:0] cos_data;
//这里添加了输出信号delta,切不可眼睛看误差,觉得差不多就行了,严谨看待、数据说话
always @(posedge sys_clk or negedge sys_rst_n) begin
if (~sys_rst_n) begin
delta <= 15'b0;
end
else if(done_ip) begin
delta <= theta - theta_ip;
end
end
//例化dds_ip核
dds_ip u_dds(
.sys_clk(sys_clk) ,
.sys_rst_n(sys_rst_n ),
.sin_data(sin_data),
.cos_data(cos_data),
.data_valid(),
.phase_valid(), // output wire m_axis_phase_tvalid
.phase_data() // output wire [15 : 0] m_axis_phase_tdata
);
//例化cordic_ip核
cordic_0 cordic_atan_inst(
.aclk(sys_clk),
.aresetn(sys_rst_n),
.s_axis_cartesian_tvalid(inrdy),
/*DDS输出的sin_data和cos_data的大概范围为-32768~32768,
因此需要这里对输入数据算术右移1位,
是因为cordic_ip核输入数据的范围只能是-1~1;对应16位数据为(-16384~16384)*/
.s_axis_cartesian_tdata({sin_data>>>1, cos_data>>>1}),
.m_axis_dout_tvalid(done_ip),
.m_axis_dout_tdata(theta_ip)
);
//例化反正切模块
Cordic_arctan uut (
.clk(sys_clk),
.rst_n(sys_rst_n),
.cordic_req(inrdy),
.cordic_ack(done),
.X(cos_data),
.Y(sin_data),
.amplitude(),
.theta(theta)
);
endmodule
重新编写测试文件tb_top如下
`timescale 1ns / 1ps
module tb_top();
reg sys_clk;
reg sys_rst_n;
reg inrdy;
initial begin
sys_clk<=1'b0;
sys_rst_n<=1'b0;
inrdy=0;
#20
sys_rst_n<=1'b1;
end
always begin
#62.5 inrdy = ~inrdy; // 第一次翻转(间隔62.5)
#12.5 inrdy = ~inrdy; // 第二次翻转(间隔12.5)
end
always #6.25 sys_clk=~sys_clk;
// always #18.75 inrdy=~inrdy;
wire done;
wire [15:0]theta;
wire done_ip;
wire [15:0]theta_ip;
top u_top(
.sys_clk(sys_clk) ,
.sys_rst_n(sys_rst_n),
.inrdy(inrdy),
.done(done), // output wire m_axis_phase_tvalid
.theta(theta), // output wire [15 : 0] m_axis_phase_tdata
.done_ip(done_ip),
.theta_ip(theta_ip)
);
endmodule
最终得到的实验结果,误差信号delta在-6~6,结果正确
四、遇到的问题
1、精度不够,误差太大
在测试用例(X,Y)=(1,1)、(10、10)时,发现误差太大,由于X、Y的值太小,在旋转过程中,不断的变小或者增大操作使得误差不断累积。联想到X、Y同比例放大不影响最终反正切函数的值,因此可以在初始赋值处,对输入数据X、Y进行左移放大2^15次方。
其次,角度值放大倍数容易造成更大的误差,角度值放大倍数不如弧度值放大倍数,因此查找表改成角度的弧度值放大2^13次方倍。
2、done信号输出错误
解决方法:从输入请求使能计算16个时钟周期后得到输出使能。
3、cordic_ip输出错误,查找发现ip核的输入需要在-1~1之间,通过sin_data>>>1, cos_data>>>1解决问题