本文对原文的代码做了一些注释和微调,为的是更加方便理解!!!

一、逐次逼近算法

逐次逼近算法流程如图 1所示,首先数据输入data[7:0],接着设置实验值D_z[3:0]和确定值D_q[3:0],然后按照从高往低的顺序,依次将每一位置1(如D_z[3]置1),再将实验值平方后与输入数据比较,若实验值的平方大于输入值(D_z^2 > data),则此位为0(D_q[3]为0),反之(D_z^2 ≤ data)此位为1(D_q[3]为1);以此迭代到最后一位。

可见,如果是n bit的数据,那么需要n/2次迭代,每次计算如果一个周期,则需要n/2个周期。

CORDIC算法基于verilog实现开根号 用verilog实现开方_sed

二、算法实现

module sqrt
	#( 	    //8,4,5
			parameter  d_width = 8,
			parameter  q_width = d_width/2,
			parameter  r_width = q_width + 1	)
	(
	input	wire clk,
	input	wire rst_n,
	input	wire i_vaild,
	input	wire [d_width-1:0]	data_i, //输入
	
	
	output	reg	o_vaild,
	output	reg	[q_width-1:0]	data_o, //输出
	output	reg	[r_width-1:0]	data_r  //余数
	
    );
//--------------------------------------------------------------------------------
//  注意这里使用了流水线操作,输出数据的位宽决定了流水线的级数,级数=q_width
	reg 	[d_width-1:0] D     [q_width:1]; //保存依次输入进来的被开方数据
	reg 	[q_width-1:0] Q_z	[q_width:1]; //保存每一级流水线的实验值
	reg	 	[q_width-1:0] Q_q	[q_width:1]; //由实验值与真实值的比较结果确定的最终值
	reg 	valid_flag		    [q_width:1]; //表示此时寄存器D中对应位置的数据是否有效
//--------------------------------------------------------------------------------
	always@(posedge	clk or negedge	rst_n)
		begin
			if(!rst_n)
				begin
					D[q_width] <= 0;
					Q_z[q_width] <= 0;
					Q_q[q_width] <= 0;
					valid_flag[q_width] <= 0;
				end
			else if(i_vaild)
				begin
					D[q_width] <= data_i;  //被开方数据
					Q_z[q_width] <= {1'b1,{(q_width-1){1'b0}}}; //实验值设置,先将最高位设为1
					Q_q[q_width] <= 0; //实际计算结果
					valid_flag[q_width] <= 1;
				end
			else
				begin
					D[q_width] <= 0;
					Q_z[q_width] <= 0;
					Q_q[q_width] <= 0;
					valid_flag[q_width] <= 0;
				end
		end
//-------------------------------------------------------------------------------
//		迭代计算过程,流水线操作
//-------------------------------------------------------------------------------
		generate
			genvar i;
                //i=3,2,1
				for(i=q_width-1;i>=1;i=i-1)
					begin:U
						always@(posedge clk or negedge	rst_n)
							begin
								if(!rst_n)
									begin
										D[i] <= 0;
										Q_z[i] <= 0;
										Q_q[i] <= 0;
										valid_flag[i] <= 0;
									end
                                //在上一时钟周期将数据读入并设置数据有效,下一个周期开始比较数据
								else	if(valid_flag[i+1])
									begin
                                        //根据根的实验值最高位置为1后的平方值与真实值的大小比较结果,
                                        //确定最高位是否应该为1以及将次高位的赋值为1,准备开始下一次比较!!!
                                        //注意,这里最后是给Q_z[i]和Q_q[i]赋值,相当于把上一周期的数据处理后
                                        //移到了寄存器的下一个位置,而Q_z[i+1]和Q_q[i+1]则负责接收新的数据
										if(Q_z[i+1]*Q_z[i+1] > D[i+1])
											begin
                                                //如果实验值的平方过大,那么就将最高位置为0,次高位置1,
                                                //并将数据从位置i+1移至下一个位置i,而i+1的位置用于接收下一个输入的数据
												Q_z[i] <= {Q_q[i+1][q_width-1:i],1'b1,{{i-1}{1'b0}}};
												Q_q[i] <= Q_q[i+1];
											end
										else
											begin
												Q_z[i] <= {Q_z[i+1][q_width-1:i],1'b1,{{i-1}{1'b0}}};
												Q_q[i] <= Q_z[i+1];
											end
										D[i] <= D[i+1];
										valid_flag[i] <= 1;
									end
								else
									begin
										valid_flag[i] <= 0;
										D[i] <= 0;
										Q_q[i] <= 0;
										Q_z[i] <= 0;
									end
							end
					end
		endgenerate
//--------------------------------------------------------------------------------
//	计算余数与最终平方根
//--------------------------------------------------------------------------------
		always@(posedge	clk or negedge	rst_n) 
			begin
				if(!rst_n)
					begin
						data_o <= 0;
						data_r <= 0;
						o_vaild <= 0;
					end
				else	if(valid_flag[1])
					begin
						if(Q_z[1]*Q_z[1] > D[1])
							begin
								data_o <= Q_q[1];
								data_r <= D[1] - Q_q[1]*Q_q[1];
								o_vaild <= 1;
							end
						else
							begin
								data_o <= {Q_q[1][q_width-1:1],Q_z[1][0]};
								data_r <= D[1] - {Q_q[1][q_width-1:1],Q_z[1][0]}*{Q_q[1][q_width-1:1],Q_z[1][0]};
								o_vaild <= 1;
							end
					end
				else
					begin
						data_o <= 0;
						data_r <= 0;
						o_vaild <= 0;
					end
			end
endmodule

Q_q会比Q_z晚更新一个周期,因为它需要利用Q_z与真实值比较后的结果选择要不要更新。

以8位数据为例,在i=1时,此时实验值Q_z已经进行了4次更新,3次比较,所以Q_q更新了3次。所以还需要最后一次Q_z比较,才可以确定最后的结果。最后一次比较的时候,Q_z的最低位已经被置1,而Q_q的最低位还没有更新。最后一次比较就不更新Q_z和Q_q了,而是直接将正确值赋值给data_o。由以上可知,开方结果比输入数据延迟n/2+1个时钟周期。

既然更新了4次Q_z,那么数据寄存器D就要至少能够保存4个输入数据。

三、设计仿真

`define  d_w 		 16
`define  q_w		 `d_w / 2
`define  r_w 		 `q_w + 1
//--------------------------------------------------------------------------------
module tb_sqrt;
//--------------------------------------------------------------------------------
	// Inputs
	reg 									clk;
	reg 									rst_n;
	reg					 					i_vaild;
	reg 			[`d_w-1:0] 				data_i;

	// Outputs
	wire 									o_vaild;
	wire 			[`q_w-1:0]				data_o;
	wire 			[`r_w-1:0]				data_r;

//--------------------------------------------------------------------------------
	// Instantiate the Unit Under Test (UUT)
	sqrt 
	#(
		.d_width    (`d_w),
		.q_width 	(`q_w),
		.r_width 	(`r_w)	
	)
		uut 
	(
		.clk			(	clk			), 
		.rst_n			(	rst_n		), 
		.i_vaild		(	i_vaild		), 
		.data_i		    (	data_i		), 
		.o_vaild		(	o_vaild		), 
		.data_o		    (	data_o		), 
		.data_r		    (	data_r		)
	);
//--------------------------------------------------------------------------------
	initial begin
		// Initialize Inputs
		clk = 0;
		rst_n = 0;
		// Wait 10 ns for global reset to finish
		#10;
        rst_n = 1; 
		// Add stimulus here

	end
    
	always #5 clk  = ~ clk ;
	
	reg	[`d_w-1:0]		cnt ;
	
//--------------------------------------------------------------------------------
	always@(posedge	clk or negedge	rst_n)
		begin
			if(!rst_n)
				begin
					i_vaild <= 0;
					data_i <= 0;
					cnt <= 0;
				end
			else	if(cnt < 10)
				begin
					i_vaild <= 1;
					data_i <= {$random} % {`d_w{1'b1}};
					cnt <= cnt + 1;
				end
			else
				begin
					i_vaild <= 0;
					data_i <= 0;
					cnt <= cnt;
				end
		end
//--------------------------------------------------------------------------------
endmodule

CORDIC算法基于verilog实现开根号 用verilog实现开方_sed_02

可以看出,和在第二部分分析的一致,开方结果比输入延迟了n/2+1个时钟周期,n为输入数据位宽。