verilog实现开方运算/基于迭代法的平方根计算算法/FPGA实现开根号算法
因老师要我们用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 @(*) begin
if (I < 0)
abs_I = -I;
else
abs_I = I;
end
always @(*) begin
if (Q < 0)
abs_Q = -Q;
else
abs_Q = Q;
end
assign 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) begin
if (rst)
begin
sum_r <= 0;
cnt <= 0;
end
else if (valid)
begin
cnt <= cnt + 1;
sum_r <= sum_r + in * 32'd1000000;
end
else if (cnt == n)
begin
sum_r <= 0;
cnt <= 0;
end
end
always @(posedge clk or posedge rst) begin
if (rst)
begin
data_valid <= 0;
sum_r2 <= 0;
end
else if (cnt == n)
begin
data_valid <= 1;
sum_r2 <= sum_r;
end
else
data_valid <= 0;
end
assign sum = sum_r2;
endmodule
4.3 sqrt模块
此模块主要根据上个模块输出的数据和valid来进行开根号运算,并且分离出来整数部分和小数部分
部分代码设计:
//状态控制
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
sqrt_en <= 1'b0;
icnt <= iteration_number - 1;
end else if (!sqrt_en) begin // 等待中
if (din_valid_i) begin
sqrt_en <= 1'b1;
icnt <= iteration_number - 1;
din_reg <= {{(DW % 2){1'b0}}, din_i}; // 输入扩展到偶数
sqrt_data <= 0;
rem_data <= 0;
end
end 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 begin
clk = 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;
end
always #5 clk = ~clk;
endmodule
5.1 理论值
首先先拿计算器运算一下,模拟数据
可以看出来,根据公式算出来为181.150,
5.2 实际值
接下来我们打开modelsim仿真看一下,得出的结果和计算值是否一致
由仿真结果可得,结果和计算值一样
制作不易,记得三连哦,给我动力,持续更新!!!