因老师要我们用verilog实现一个算法,涉及到开根号运算,正好学习一下算法,记录一下我的学习记录
主要算法:
要求:
输入信号:input signed [15:0] a, //数据a
输入信号:input signed [15:0] b, //数据b
输入信号:input [15:0] n, //数据个数
输入信号:valid, //数据有效
输出信号:output [15:0] result_int, //输出结果整数部分
输出信号:output [15:0] result_dec, //输出结果小数部分
输入信号a,b为有符号数。
要求算法结果保留到小数点后三位。
完整工程文件下载:verilog实现开根号算法完整工程 (点击蓝色字体获取)
1. 引言
- 背景与动机: 平方根计算在硬件中的重要性,如在图像处理、信号处理、数字滤波器等领域的应用。
- 设计目标: 实现一个高效的硬件平方根计算器,采用迭代法(如牛顿迭代法)来进行计算。
- 文章结构: 概述文章结构,明确每一部分的内容,告诉读者你将从算法原理、硬件设计实现、性能分析等方面进行讨论。
2. 开发工具
开发平台:vivado
仿真平台:modelsim
3. 平方根计算的基本原理
3.1 平方根的定义
平方根是指一个数的平方等于给定数。例如,平方根计算问题可以表述为:给定一个非负数 SS,找到一个数 xx,使得 x2=Sx2=S。
3.2 牛顿迭代法
牛顿迭代法是一种常用的逼近函数根的方法,在求解平方根问题时具有广泛应用。牛顿迭代法用于平方根的计算公式如下:
Xn+1=1/2(Xn+S/Xn)
其中:
- S 是我们要求平方根的数;
- Xn 是第 n 次迭代的近似值;
- Xn+1 是下一个迭代的近似值。
这个公式表示:通过当前的近似值 Xn 和 S,我们可以得到一个新的更接近的平方根值 Xn+1。
3.3 迭代法的优点
牛顿迭代法具有以下优点:
- 快速收敛: 牛顿法的收敛速度非常快,通常经过几次迭代即可获得很高的精度;
- 实现简单: 只需要基本的加法、除法和位移操作,适合在硬件中实现。
4. verilog实现
总体设计框架
主要分为3大模块:
square_calculator:判断输入数据正负,并对其平方,组合逻辑
accumulator:对平方的数据进行求和,并除以n+1
sqrt:对上一个模块的结果,进行开根号运算,且在data_vaild为高的时候才算
4.1 square_calculator模块
此模块主要是来判断输入数据正负,并对其进行平方,使用的是组合逻辑,所以并不需要时钟输入
代码设计:
module square_calculator (input signed[15:0] I,input signed[15:0] Q,output [31:0] I_squIre,output [31:0] Q_squIre
);reg signed [15:0] abs_I;reg signed [15:0] abs_Q;always @(*) beginif (I < 0)abs_I = -I;elseabs_I = I;endalways @(*) beginif (Q < 0)abs_Q = -Q;elseabs_Q = Q;endassign I_squIre = abs_I * abs_I;assign Q_squIre = abs_Q * abs_Q;
endmodule
4.2 accumulator模块
此模块主要用来计算根号内容,并输出对应的valid信号,但是为了满足精确到小数点后三位,需要把输入数据乘上1000_000,然后再开完根号之后再除以1000即可得到小数部分和整数部分。
部分代码设计:
module accumulator (input clk,input rst,input valid,input [15:0] n,input [31:0] in,output reg data_valid,output [63:0] sum
);reg [15:0] cnt;reg [63:0] sum_r;reg [63:0] sum_r2;always @(posedge clk or posedge rst) beginif (rst)beginsum_r <= 0;cnt <= 0;endelse if (valid)begincnt <= cnt + 1;sum_r <= sum_r + in * 32'd1000000; endelse if (cnt == n)beginsum_r <= 0;cnt <= 0;endendalways @(posedge clk or posedge rst) beginif (rst)begindata_valid <= 0;sum_r2 <= 0;endelse if (cnt == n)begindata_valid <= 1;sum_r2 <= sum_r;end else data_valid <= 0;endassign sum = sum_r2;endmodule
4.3 sqrt模块
此模块主要根据上个模块输出的数据和valid来进行开根号运算,并且分离出来整数部分和小数部分
部分代码设计:
//状态控制
always @(posedge clk or negedge rst_n) beginif (~rst_n) beginsqrt_en <= 1'b0;icnt <= iteration_number - 1;end else if (!sqrt_en) begin // 等待中if (din_valid_i) beginsqrt_en <= 1'b1;icnt <= iteration_number - 1;din_reg <= {{(DW % 2){1'b0}}, din_i}; // 输入扩展到偶数sqrt_data <= 0;rem_data <= 0;endend else begin // 迭代中icnt <= icnt - 1;din_reg <= {din_reg[din_width-3:0], 2'b00};sqrt_data <= {sqrt_data[sqrt_width-2:0], sqrt_next};rem_data <= rem_next;if (icnt == 0) sqrt_en <= 1'b0; // 结束迭代end
end
5. 仿真验证
编写仿真文件tb_sqrt_mean_calculator
`timescale 1ns / 1ps
module tb_sqrt_mean_calculator;reg clk, rst, valid;reg signed[15:0] I, Q, n;wire [15:0] result_int,result_dec;wire [31:0] result;sqrt_mean_calculator uut (.clk(clk),.rst(rst),.valid(valid),.I(I),.Q(Q),.n(n),.result(result),.result_int(result_int),.result_dec(result_dec));initial beginclk = 0;rst = 1;valid = 0;#10 rst = 0;n = 16'd3; // 数据总数为n+1// 输入数据#10 valid = 1; I = 16'd1; Q = 16'd2; // 第1组数据#10 I = -16'd11; Q = 16'd12; // 第2组数据#10 I = 16'd100; Q = -16'd112; // 第3组数据#10 I = -16'd212; Q = -16'd252; // 第4组数据#10 valid = 0;#600/*// 输入数据#10 vIlid = 1; I = 16'd4; Q = 16'd2; // 第1组数据#10 I = 16'd7; Q = 16'd3; // 第2组数据#10 I = 16'd3; Q = 16'd4; // 第3组数据#10 I = 16'd2; Q = 16'd1; // 第4组数据#10 I = 16'd5; Q = -16'd1; // 第5组数据#10 vIlid = 0;#600*/$stop;endalways #5 clk = ~clk;
endmodule
5.1 理论值
首先先拿计算器运算一下,模拟数据
可以看出来,根据公式算出来为181.150,
5.2 实际值
接下来我们打开modelsim仿真看一下,得出的结果和计算值是否一致
由仿真结果可得,结果和计算值一样
制作不易,记得三连哦,给我动力,持续更新!!!