三种常见平方根算法的电路设计及Verilog实现与仿真

这篇具有很好参考价值的文章主要介绍了三种常见平方根算法的电路设计及Verilog实现与仿真。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、平方根及三种常见平方根算法简介

数学是物理的基础,是广大世界的基本组成部分,而数学运算是数学理论的核心部分,数学运算有加减乘除乘方等基本运算,拓展的运算里有一项是开方运算,开方运算在数字计算、图形显示等领域具有重要的地位,所以如何在硬件上实现该运算可以提高计算单元的性能,加快计算速度。

本文实现的算法包括二分迭代法、牛顿迭代法、逐次逼近法,前两种方法来源于数值计算方法,第三种方法类似于逐次渐进型ADC的原理,以下分别介绍这三种算法。本篇文章约定被开方数为16位无符号数,输出开方结果为8位无符号数,采用多时钟周期计算结果。

(一)、二分迭代法

二分法本质上是一种区间迭代算法,通过不断缩小隔根区间长度使区间中点不断逼近根值。对于求一个连续函数f(x)在[a,b]区间上等于0的根,首先判断是否f(a)*f(b)<0,若小于0则包含根,若大于0那么判断是否单调,若单调则无根若不单调则含多根。以下简介它的算法过程:

第一,计算y1=f(a),y2=f(b);

第二,计算x0=0.5*(a+b),y0=f(x0),若|y0|<ε0,则输出x0结束否则转第三步;

第三,若y0*y1<0,则置b=x0,y2=y0,否则置a=x0,y1=y0,转第四步;

第四,若|b-a|>ε1,则转第二步,否则输出x0结束。

(二)、牛顿迭代法

牛顿迭代法是一种局部收敛的算法,它的作法是在方程f(x)=0的根的邻近点x0用f(x)的泰勒展开式的前两项代替f(x),得f(x0)+f'(x0)(x-x0)=0,如果f'(x0)!=0,可得到根的近似值x1=x0-f(x0)/f'(x0)。重复以上过程得到迭代公式verilog实现开根号,算法,fpga开发。以下是算法过程:

第一,输入根的初试近似值x0以及允许误差ε,置n=0;

第二,计算verilog实现开根号,算法,fpga开发

第三,判断,若|xn+1-xn|>=ε,则置n=n+1,转第二步,否则输出xn+1结束。 

(三)、逐次逼近法

在平时的生活中我们会用到天平来称物体的重量,那么称重的过程是怎么样的呢?

首先我们先放一个最大重量的砝码,如果太重了表明这个砝码应该不用加,如果太轻了表明这个砝码应该加着,接着我们放一个第二重量的砝码,重复以上的判断过程,接着第三个,第四个...直到最轻的砝码判断完毕或者天平达到平衡结束称重。

逐次逼近法也是如此,对于一个定宽的数据din,假设为16bits,开方后得到的数result应该是8bits的,那么我们可以对8bits的数进行称重过程的放置判断操作,首先最高位MSB置为1,其余低位为0判断平方后与din比较,如果大了表示这个最高位应该为0,如果小了表示这个最高位应该为1,接着判断次高位,重复置1、平方、比较、确定数值的过程,依次计算下一位直到最低位LSB结束得到结果。以下是算法过程:

第一,从高到低将上一次置位的下一低位置1,该位以下的低位置零,若没有上一位则置最高位,若没有以下的低位则运算结束,转第二步;

第二,将第一步得到的数平方,与原数比较,若比原数大则上一步里置1的那一位确定置0,若比原数小则上一步里置1的那一位确定置1,转第一步。

二、Verilog状态机的描述

状态机来源于数字电路里的时序逻辑电路,设计状态机的方法就是设计数字电路时序逻辑电路的方法,包括状态编码,状态转换图的绘制,状态转换表的建立,状态方程、输出方程、驱动方程的书写,竞争冒险的检查和自启动的检查。

状态机有摩尔Moore型和米利Mealy型,其中Moore型输出仅依赖于状态,Mealy型输出与状态和输入有关。采用Verilog描述的状态机有两段式和三段式,两段式性能最好但时序差,三段式时序性能兼顾。

两段式描述分为状态时序段state timing和状态跳转段state jump。状态时序段采用时序逻辑always@(posedge clk)非阻塞赋值语句描述现态cur_state到次态nxt_state的转换,状态跳转段采用组合逻辑always@(*)阻塞赋值语句配合case、if-else根据现态描述次态和输出的逻辑。

三段式描述分为状态时序段和状态跳转段和输出信号段。状态时序段和状态跳转段与二段式描述一致,只是将输出信号的输出逻辑的描述单独拿出来在输出信号段采用时序逻辑always@(posedge clk)根据次态nxt_state生成输出信号。

三、算法的Verilog实现

在使用Verilog描述电路前必须做到心中有电路,这是采用HDL设计数字电路的前提。数字电路根据功能大体上可以分为数据通路和控制通路,至于先设计哪一部分取决于总体电路的功能是偏向数据处理运算还是偏向控制。根据以上的说明将对以下三种算法进行电路设计并用Verilog描述。

(一)、二分迭代法

由于在无符号二进制数运算里没有乘积小于零的判断结果,因此每次求出平均值后作平方与被开方数比较,若小于等于被开方数则将平均值赋给左操作数,若大于等于平均值则将平均值赋给右操作数。

以'd95为例,初始左操作数a='d0,右操作数b='d15。

第一次,('d0+'d15)>>1='d7,('d7)^2='d49<'d95,令a='d7;

第二次,('d7+'d15)>>1='d11,('d11)^2='d121>'d95,令b='d11;

第三次,('d7+'d11)>>1='d9,('d9)^2='d81<'d95,令a='d9;

第四次,('d9+'d11)>>1='d10,('d10)^2='d100>'d95,令b='d10,因为此时a='d9,b='d10,两者相差1'b1,因此无需再做下一次运算,输出a即结束。若中途出现a==b也即结束运算,输出a。

首先分析运算过程考虑器件复用,决定采用时序电路多周期计算。可以将控制通路的状态划分为三个状态:IDLE,CALC,DONE。IDLE表示空闲,只有输入一个使能信号calc_en才能启动计算,即进入CALC状态,这个状态主要用于输出数据通路使用的触发器flip-flop的使能端,用以存储计算中产生的信号,待计算达到一定的程度时输入信号calc_end有效后状态进入DONE,即完成状态,此时输出done信号表示计算结束。为了比较各个算法实现的电路的性能,在CALC状态还应该输出一个计算器的计数使能,用于对计算所用时钟周期的计数。通过以上分析可以得到以下的状态转换图和输出信号表。

verilog实现开根号,算法,fpga开发

状态 操作 ff_en cnt_en done

IDLE

空闲 1'b0 1'b0 1'b0
CALC 计算 1'b1 1'b1 1'b0
DONE 完成 1'b0 1'b0 1'b1

 以下是数据通路电路图

通过result乘方与din比较决定是否刷新左右操作数的选择信号selLeft和selRight。

verilog实现开根号,算法,fpga开发

 result的输出逻辑

verilog实现开根号,算法,fpga开发

 那么问题来了,怎么判断计算结束了呢?我们通过上文二分法的例子发现计算完成时左操作数与右操作数不是相等就是差1,于是可以有以下的逻辑输出calc_end,再输入给状态机使状态跳转。

verilog实现开根号,算法,fpga开发

 经过以上分析已经可以用Verilog描述电路了,模块名为mysqrt1,文件名一致。

/******************************************************************************
* mysqrt1.v
*******************************************************************************/

module mysqrt1(
  input        clk,
  input        calc_en,
  input        rst_n,
  input [15:0] din,
  output [7:0] result_o,
  output [3:0] cnt_clk,
  output       done_o
);

encode state
  localparam IDLE = 2'b00;
  localparam CALC = 2'b01;
  localparam DONE = 2'b10;
	
middle signals
  reg  [1:0]  cur_state,nxt_state;//state
  reg         ff_en;//enable flip-flop
  reg         cnt_en;//enable counter
  reg         done;//finish
  reg  [8:0]  opLeft,opRight;//for operation "opLeft"+"opRight"
  wire [7:0]  result;//store result
  wire [8:0]  adder_tmp;//temp adder output data
  wire [8:0]  opLeft_tmp;//temp opLeft data
  wire [8:0]  opRight_tmp;//temp opRight data
  wire        opOr1,opOr2;//gate Or inputs
  wire [15:0] multi_tmp;//temp multi out data
  wire        calc_end;//end calculate
  wire        co;//from counter
  wire        selLeft,selRight;//select store which to opLeft and opRight


assign output signals
  assign result_o = result;
  assign done_o   = done;

state timing
  always@(posedge clk,negedge rst_n) begin
    if(!rst_n)
	  cur_state <= IDLE;
    else
	  cur_state <= nxt_state;
  end

state jump->nxt_state
  always@(*) begin
    case(cur_state)
	  IDLE:nxt_state = calc_en  ? CALC : IDLE;
	  CALC:nxt_state = calc_end ? DONE : CALC;
	  DONE:nxt_state = IDLE;
	  default:nxt_state = IDLE;
    endcase
  end

control signals logic to data path
  always@(posedge clk,negedge rst_n) begin
    if(!rst_n) begin
	  ff_en  <= 1'b0;
	  cnt_en <= 1'b0;
	  done   <= 1'b0;
    end
    else
	  case(nxt_state)
		IDLE:begin
		  ff_en  <= 1'b0;
		  cnt_en <= 1'b0;
		  done   <= 1'b0;
		end
		CALC:begin
		  ff_en  <= 1'b1;
		  cnt_en <= 1'b1;
		  done   <= 1'b0;
		end
		DONE:begin
		  ff_en  <= 1'b0;
		  cnt_en <= 1'b0;
		  done   <= 1'b1;
		end
		default:begin
		  ff_en  <= 1'b0;
		  cnt_en <= 1'b0;
		  done   <= 1'b0;
		end
	  endcase
  end

data path
//counter
  cnt_ceil cnt_ceil_x(
    .clk   (clk),
    .en    (cnt_en),
    .rst_n (rst_n),
    .ceil  (4'b1111),
    .cnt   (cnt_clk),
    .co    (co)
  );
//"selLeft" and "selRight" logic
  assign multi_tmp = result * result;
  assign selLeft   = (multi_tmp <= din);
  assign selRight  = (multi_tmp >= din);
//"calc_end" logic
  assign opOr1    = ((1'b1+opLeft)==opRight);
  assign opOr2    = (opLeft==opRight);
  assign calc_end = opOr1 || opOr2;
//"result" logic
  assign opLeft_tmp  = selLeft  ? {1'b0,result} : opLeft;
  assign opRight_tmp = selRight ? {1'b0,result} : opRight;
  assign adder_tmp   = opLeft + opRight;
  assign result      = adder_tmp[8:1];
  always@(posedge clk,negedge rst_n) begin
    if(!rst_n) begin
      opLeft  <= 9'b0_0000_0000;//set left to minimal
      opRight <= 9'b0_1111_1111;//set right to maximal
    end
    else
	  if(ff_en) begin
        opLeft  <= opLeft_tmp;
        opRight <= opRight_tmp;
      end
  end
  
endmodule

计数器模块cnt_ceil代码如下。

/******************************************************************************
* cnt_ceil.v
*******************************************************************************/

module cnt_ceil(
  input        clk,
  input        en,
  input        rst_n,
  input  [3:0] ceil,
  output [3:0] cnt,
  output       co
);
  reg [3:0] cnt_temp;
  always @(posedge clk,negedge rst_n) begin
	if(!rst_n)
	  cnt_temp <= 4'b0000;
	else 
	  if(en)
	    if(cnt_temp>=ceil)
          cnt_temp <= 4'b0000;
        else
		  cnt_temp <= cnt_temp+1'b1;
  end
  assign cnt = cnt_temp;
  assign co  = en && (cnt_temp==ceil);
endmodule

(二)、牛顿迭代法

电路分为控制通路和数据通路,控制通路与二分法一致,不再赘述。以下分析数据通路。

计算的核心是公式x=(a/x+x)/2,使用除法器和加法器可以构成计算核心。如下图所示。

verilog实现开根号,算法,fpga开发

 问题是怎么判断计算结束了呢?

举个例子,被开方数a='d5343,初始迭代数x='d255。

第一次,(5343/255+255)/2=137;第二次,(5343/137+137)/2=88;

第三次,(5343/88+88)/2=74;第四次,(5343/74+74)/2=73;第五次,(5343/73+73)/2=73

可以发现经过迭代后最后中间数稳定不变即可判断计算结束,还发现最终的结果与上一次迭代的结果仅差1,那么也可由此判断计算已经结束无需作下一次迭代。于是可以画出以下的电路,输出calc_end,再输入给状态机使状态跳转。

verilog实现开根号,算法,fpga开发

经过以上分析,可以用Verilog描述电路,定义模块名为mysqrt2,文件名一致。

/******************************************************************************
* mysqrt2.v
*******************************************************************************/

module mysqrt2(
  input        clk,
  input        calc_en,
  input        rst_n,
  input [15:0] din,
  output [7:0] result_o,
  output [3:0] cnt_clk,
  output       done_o
);

encode state
  localparam IDLE = 2'b00;
  localparam CALC = 2'b01;
  localparam DONE = 2'b11;

middle signals
  reg  [1:0] cur_state,nxt_state;//state
  reg  [7:0] result;//result
  reg        done;//finish
  reg        ff_en;//enable flip-flop store
  reg        cnt_en;//enable counter
  wire       calc_end;//end calculate
  wire [7:0] div_tmp;//temp divide data
  wire [8:0] opAdder1,opAdder2;//to adder
  wire [8:0] adder_tmp;//output from adder
  wire [7:0] r_tmp;//temp result
  wire       opOr1,opOr2;//to gate Or
  wire       co;//counter output
  

assign output signals
  assign result_o = result;
  assign done_o   = done;

state timing
  always@(posedge clk,negedge rst_n) begin
    if(!rst_n)
	  cur_state <= IDLE;
    else
	  cur_state <= nxt_state;
  end

state jump->nxt_state
  always@(*) begin
    case(cur_state)
	  IDLE:nxt_state = calc_en  ? CALC : IDLE;
	  CALC:nxt_state = calc_end ? DONE : CALC;
	  DONE:nxt_state = IDLE;
	  default:nxt_state = IDLE;
    endcase
  end

control signals logic to data path
  always@(posedge clk,negedge rst_n) begin
    if(!rst_n) begin
	  ff_en  <= 1'b0;
	  cnt_en <= 1'b0;
	  done   <= 1'b0;
    end
    else
	  case(nxt_state)
        IDLE:begin
	      ff_en  <= 1'b0;
	      cnt_en <= 1'b0;
	      done   <= 1'b0;
	    end
        CALC:begin
	      ff_en  <= 1'b1;
	      cnt_en <= 1'b1;
	      done   <= 1'b0;
	    end
        DONE:begin
	      ff_en  <= 1'b0;
	      cnt_en <= 1'b0;
	      done   <= 1'b1;
	    end
        default:begin
	      ff_en  <= 1'b0;
	      cnt_en <= 1'b0;
	      done   <= 1'b0;
	    end
	  endcase
  end

data path
//counter
  cnt_ceil cnt_ceil_1(
    .clk   (clk),
    .en    (cnt_en),
    .rst_n (rst_n),
    .ceil  (4'b1111),
    .cnt   (cnt_clk),
    .co    (co)
  );
//"calc_end" logic
  assign opOr1    = ((r_tmp+1'b1)==result);
  assign opOr2    = (r_tmp==result);
  assign calc_end = opOr1 || opOr2;
//"result" logic
  assign div_tmp   = din / result;
  assign opAdder1  = {1'b0,div_tmp};
  assign opAdder2  = {1'b0,result};
  assign adder_tmp = opAdder1 + opAdder2;
  assign r_tmp     = adder_tmp[8:1];
  always@(posedge clk,negedge rst_n) begin
    if(!rst_n)
	  result <= 8'b1111_1111;//set to maximal---'d255
    else
	  if(ff_en)
		result <= r_tmp;
  end
endmodule

(三)、逐次逼近法

控制通路的状态机与二分法一致,不再赘述。以下分析数据通路。

数据通路的关键是如何依次对result每一位判断是否为1,这里引入中间信号index[2:0],用来记录当前应该对哪一位数操作,这里的index可以为计数器输出的低三位,再由index和乘法器比较器输出中间信号judge,用来判断该位是否为1,由index和常数进行比较产生selx信号,作为MUX的选择信号,最后用judge和selx驱动MUX输出result的每一位。

verilog实现开根号,算法,fpga开发

 verilog实现开根号,算法,fpga开发

verilog实现开根号,算法,fpga开发

 verilog实现开根号,算法,fpga开发

 定义模块名为mysqrt3,Verilog文件名一致。

/******************************************************************************
*mysqrt3.v
*******************************************************************************/
module mysqrt3(
  input         clk,
  input         calc_en,
  input         rst_n,
  input  [15:0] din,
  output [7:0]  result_o,
  output        done_o
);
//
//encode states
  localparam IDLE = 2'b00;
  localparam CALC = 2'b01;
  localparam DONE = 2'b10;
/
//middle signals
  reg  [1:0] cur_state,nxt_state;//state
  reg        done;//done
  reg  [7:0] result;//store result
  reg        cnt_en;//enable counter
  wire [3:0] cnt;//counter output
  wire [2:0] index;//calculated index from 'd0 to 'd7
  wire       judge;//judge if result[index] is 1'b1
  wire       co;//counter output co
  wire       sel0;//if index=='d7
  wire       sel1;//if index=='d6
  wire       sel2;//if index=='d5
  wire       sel3;//if index=='d4
  wire       sel4;//if index=='d3
  wire       sel5;//if index=='d2
  wire       sel6;//if index=='d1
  wire       sel7;//if index=='d0
  reg        ff_en;//enable store result
  wire       calc_end;//calculate end signal
  wire       r0_tmp,r1_tmp,r2_tmp,r3_tmp,r4_tmp,r5_tmp,r6_tmp,r7_tmp;//temp data
  wire       j_tmp;//temp data
  reg  [7:0] op_tmp;//temp data
  wire [15:0] multi_tmp;//temp data
/
//assign output signals
  assign result_o = result;
  assign done_o   = done;
/
//state timing
  always @(posedge clk,negedge rst_n) begin
    if(!rst_n)
	  cur_state <= IDLE;
    else
	  cur_state <= nxt_state;
  end
/
//state jump
  always @(*) begin
	case(cur_state)
	  IDLE:nxt_state = calc_en ? CALC : IDLE;
	  CALC:nxt_state = calc_end ? DONE : CALC;
	  DONE:nxt_state = IDLE;
	  default:nxt_state = IDLE;
	endcase
  end
/
//control signals logic to data path  
  always @(posedge clk,negedge rst_n) begin
    if(!rst_n) begin
	  ff_en  <= 1'b0;
	  cnt_en <= 1'b0;
	  done   <= 1'b0;
    end
    else
	  case(nxt_state)
		IDLE: begin
		  ff_en  <= 1'b0;
	          cnt_en <= 1'b0;
		  done   <= 1'b0;
		end
		CALC: begin
		  ff_en  <= 1'b1;
		  cnt_en <= 1'b1;
		  done   <= 1'b0;
		end
		DONE: begin
		  ff_en  <= 1'b0;
		  cnt_en <= 1'b0;
		  done   <= 1'b1;
		end
		default: begin
		  ff_en  <= 1'b0;
	      cnt_en <= 1'b0;
		  done   <= 1'b0;
		end
		
	  endcase
  end
/
data path
//"index" logic
  cnt_ceil cnt_ceil_0(
    .clk   (clk),
    .en    (cnt_en),
    .rst_n (rst_n),
    .ceil  (4'd7),
    .cnt   (cnt),
    .co    (co)
	  );
  assign index = cnt[2:0];
//"judge" logic
  always @(*) begin
    case(index)
	  3'd0:op_tmp = 8'b1000_0000;
	  3'd1:op_tmp = {result[7],7'b100_0000};
	  3'd2:op_tmp = {result[7:6],6'b10_0000};
	  3'd3:op_tmp = {result[7:5],5'b1_0000};
	  3'd4:op_tmp = {result[7:4],4'b1000};
	  3'd5:op_tmp = {result[7:3],3'b100};
	  3'd6:op_tmp = {result[7:2],2'b10};
	  3'd7:op_tmp = {result[7:1],1'b1};
    endcase
  end
  assign multi_tmp = op_tmp * op_tmp;
  assign judge     = (multi_tmp <= din);
//"selx" logic
  assign sel7 = (index==3'd0);
  assign sel6 = (index==3'd1);
  assign sel5 = (index==3'd2);
  assign sel4 = (index==3'd3);
  assign sel3 = (index==3'd4);
  assign sel2 = (index==3'd5);
  assign sel1 = (index==3'd6);
  assign sel0 = (index==3'd7);
//"result" logic
  assign j_tmp  = judge ? 1'b1  : 1'b0;
  assign r7_tmp = sel7  ? j_tmp : result[7];
  assign r6_tmp = sel6  ? j_tmp : result[6];
  assign r5_tmp = sel5  ? j_tmp : result[5];
  assign r4_tmp = sel4  ? j_tmp : result[4];
  assign r3_tmp = sel3  ? j_tmp : result[3];
  assign r2_tmp = sel2  ? j_tmp : result[2];
  assign r1_tmp = sel1  ? j_tmp : result[1];
  assign r0_tmp = sel0  ? j_tmp : result[0];
  always @(posedge clk,negedge rst_n) begin
    if(!rst_n) begin
	  result <= 8'b0000_0000;
    end
    else
	  if(ff_en) begin
	    result[7] <= r7_tmp;
	    result[6] <= r6_tmp;
	    result[5] <= r5_tmp;
	    result[4] <= r4_tmp;
	    result[3] <= r3_tmp;
	    result[2] <= r2_tmp;
	    result[1] <= r1_tmp;
	    result[0] <= r0_tmp;
	  end
  end
//"calc_end" logic
  assign calc_end = co;

endmodule

四、进行仿真

(一)统一的testbench

用Verilog编写一个统一的testbench,在其中分别例化三个算法实现模块。

result1对应mysqrt1的结果,result2对应mysqrt2的结果,result3对应mysqrt3的结果;

done1对应mysqrt1的完成,done2对应mysqrt2的完成,done3对应mysqrt3的完成;

rst_n1对应mysqrt1的异步重置,rst_n2对应mysqrt2的异步重置,rst_n3对应mysqrt3的异步重置;

在每个模块的每次计算完毕后使用rst_nx异步重置内部寄存器数据并输入新的din进行下一次运算。

定义模块名为mysqrt_tb,文件名一致,程序如下。

//testbenchf for mysqrt1 and mysqrt2 and mysqrt3
//mysqrt_tb.v
`timescale 1ns/1ns
module mysqrt_tb();
  reg        clk;
  reg        ensqrt1,ensqrt2,ensqrt3;
  reg        rst_n1,rst_n2,rst_n3;
  reg [15:0] din1,din2,din3;
  wire [7:0] result1,result2,result3;
  wire       done1,done2,done3;
  wire [3:0] cnt_clk1;
  wire [3:0] cnt_clk2;
//initialize
  initial begin
    clk     = 1'b0;
    ensqrt1 = 1'b1;
    ensqrt2 = 1'b1;
    ensqrt3 = 1'b1;
    rst_n1  = 1'b1;
    rst_n2  = 1'b1;
    rst_n3  = 1'b1;
    din1    = 'd95;
    din2    = 'd95;
    din3    = 'd95;
  end
  initial begin//clk
    forever #1 clk = ~clk;
  end
  initial begin//the first rst_n
    #1 rst_n1 = 1'b0; rst_n2 = 1'b0; rst_n3 = 1'b0;
    #1 rst_n1 = 1'b1; rst_n2 = 1'b1; rst_n3 = 1'b1;
  end
//din1 and rst_n1
  always@(posedge clk,posedge done1) begin
    if(done1) begin//when done1 is pulled high
      #2 rst_n1 <= 1'b0;
         din1   <= {$random}%16'b1111_1111_1111_1111;
      #1 rst_n1 <= 1'b1;
    end
  end
//din2 and rst_n2
  always@(posedge clk,posedge done2) begin
    if(done2) begin//when done2 is pulled high
      #2 rst_n2 <= 1'b0;
         din2   <= {$random}%16'b1111_1111_1111_1111;
      #1 rst_n2 <= 1'b1;
    end
  end
//din3 and rst_n3
  always@(posedge clk,posedge done3) begin
    if(done3) begin//when done3 is pulled high
      #2 rst_n3 <= 1'b0;
         din3   <= {$random}%16'b1111_1111_1111_1111;
      #1 rst_n3 <= 1'b1;
    end
  end
//instances
  mysqrt1 mysqrt1_0(
    .clk      (clk),
    .calc_en  (ensqrt1),
    .rst_n    (rst_n1),
    .din      (din1),
    .result_o (result1),
    .cnt_clk  (cnt_clk1),
    .done_o   (done1)
  );
  mysqrt2 mysqrt2_0(
    .clk      (clk),
    .calc_en  (ensqrt2),
    .rst_n    (rst_n2),
    .din      (din2),
    .result_o (result2),
    .cnt_clk  (cnt_clk2),
    .done_o   (done2)
  );
  mysqrt3 mysqrt3_0(
    .clk      (clk),
    .calc_en  (ensqrt3),
    .rst_n    (rst_n3),
    .din      (din3),
    .result_o (result3),
    .done_o   (done3)
  );

endmodule

(二)、波形结果

附上使用Modelsim仿真的波形结果

verilog实现开根号,算法,fpga开发

附上使用Verilator和GTKWave的仿真结果

使用Verilator仿真基于Verilog的testbench可以参考我写的另一篇博客:使用Verilator仿真基于Verilog的testbench并用GTKWave查看波形。

verilog实现开根号,算法,fpga开发

五、分析与反思

二分法

计算性能取决于起始区间的位置,由于设计中没有设定读入起始区间的信号,而且被开方数遍布于整个16位空间,只能将区间设为最大,即左为0,右为’b1111_1111=’d255,这就使得每次计算都需要8个周期。那么为什么是8周期呢?首先寻找一个4位数用最大区间需要找4次,这好比在一个边长为4的正方形里找一个边长为1的正方形x,每次划分后剩下区域都为先前的一半,经过4次迭代才找到x,所以找8位数需要8周期,再经过一周期的拉高done信号的周期,总共9周期。有一个问题是数据通路中左右操作数经过加法器和乘法器的串联再经过MUX回到触发器D端,中间的延时太长了,如果在加法和乘法中间加一级寄存器则会减小延时,但是这会导致计算周期翻倍为16周期,所以这是时序和性能的权衡,这个问题和权衡在牛顿法中(除法器和加法器串联)也是如此。

verilog实现开根号,算法,fpga开发

牛顿法

性能最好,但是用了一个除法器。设计中为了满足存储中间数据的定宽9位数不溢出,迭代初始值设为’b1111_1111=’d255,在较大的数输入时能够较快地迭代出结果(2-4周期),但是遇到较小的数比如’d95就需要迭代7周期。因为输出一定是介于0到255,所以设想如果将初始值设为’d127是否能对所有数据迭代周期平均化为4周期,数学上是可行的,但是当前的电路不能满足要求,因为中间数据(a/x+x)/2可能会超出9位造成结果不收敛无法拉高calc_end信号,这里计算一下在初始值为127时最大的中间数据,(65534/127+127)=643=’b10_1000_0011,有十位数,除2后为9位数,那么存储除法结果应该用10位数,计算加法应该用10位数,而存储加法除二后的结果应该用9位数,这样才不会数据溢出。其实testbench里随机的数据最大为65534,如果为65535(‘b1111_1111_1111_1111),计算的中间数据其实是’b10_0000_0000,这是十位数,在当前设计中也是会数据溢出的,如果不改变设计会导致错误,因为下一次计算会得到除数为0的情况。除了修改设计外的解决办法是额外增加逻辑判断输入的数是否为65535,是则直接输出结果为255结束,否则按初始值为255迭代计算。

逐次逼近法

性能最稳定,计算周期为8周期,最后完成状态输出加1周期,使用了一个乘法器和较多的比较器,result每一位的触发器使能在计算状态均处于有效状态增加了动态功耗,文中的selx信号是由比较器组输出的,与写操作的每一位的触发器一一对应,于是思考可以由selx直接作为触发器的使能端而不需由控制通路输出,那么设计可以改成由index作为输入的三-八译码器输出独热码作为selx组合同时也作为触发器的使能端组合,使得仅对当前操作位的触发器写入而对其余触发器均写无效以减少动态功耗和面积。还有一个可以改进的地方是当中间数据平方后等于输入其实已经可以结束计算了,但是本文的设计中没有该判断逻辑,所以无论怎样都要计算8周期,对于很多数这是多余的,有兴趣的读者可以自己设计加上该逻辑。

*本文设计均单纯考虑实现算法不考虑面积时序功耗,仍有不足之处,欢迎读者给出评论建议。文章来源地址https://www.toymoban.com/news/detail-609223.html

到了这里,关于三种常见平方根算法的电路设计及Verilog实现与仿真的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 平方根法、改进的平方根法解方程组

    本篇内容包含两个部分:平方根法、改进的平方根法。感觉这种题绝大部分是靠套公式,记住公式和解题思路,还是相当简单的。 1 平方根法 1.1 解题思路 1.2 核心公式 1.3 例题解析 由 Ly=b L^t*x=y 解得 2 改进的平方根法 2.1 为什么要使用改进的平方根法 2.2 改进的平方根法解题公

    2024年02月06日
    浏览(46)
  • Python求平方根

    Python求平方根的方法有很多种,但是在不同情况下使用也不同 方法一:常用的是math模块的sqrt()函数 方法二:math模块的pow()函数 方法三:使用内置函数pow() 有时候math模块无法使用,这时候就需要使用自带的内置函数pow() 方法四:使用指数运算符** 注:将其中的1/2换成1/3则是

    2024年02月05日
    浏览(36)
  • leetcode69---x 的平方根

    大家好,我是大唐,刚刷完了几道经典的leetcode题,今天给大家分享一道leetcode上面的二分查找经典题型---x 的平方根,我们往下看。 给你一个非负整数  x  ,计算并返回  x  的  算术平方根  。 由于返回类型是整数,结果只保留  整数部分  ,小数部分将被  舍去 。 注意

    2024年03月18日
    浏览(42)
  • Python算法例4 求平方根

    实现int sqrt(int x)函数,计算并返回x的平方根。 sqrt(3)=1;sqrt(4)=2;sqrt(5)=2;sqrt(17)=4。 要实现计算整数x的平方根函数sqrt(x),可以使用二分查找法。 首先,我们定义一个变量left = 0用来表示搜索区间的左边界,以及一个变量right = x用来表示搜索区间的右边界。初

    2024年02月05日
    浏览(39)
  • leetcode69 x 的平方根

    题目变形为找到 f ( x ) = x 2 − c = 0 f(x)=x^2-c=0 f ( x ) = x 2 − c = 0 的根,其中 x x x 是非负整数。由于 f ( 0 ) = − c ≤ 0 , f ( c ) = c 2 − c ≥ 0 f(0)=-cle0,f(c)=c^2-cge0 f ( 0 ) = − c ≤ 0 , f ( c ) = c 2 − c ≥ 0 ,则 [ 0 , c ] [0,c] [ 0 , c ] 之间必然存在一个根,使用二分法。 但是由于计算

    2024年02月22日
    浏览(42)
  • C语言—求平方根(sqrt函数)

            在数学当中,我们知道了平方根。那么在C语言当中求一个数的平方根是如何实现的呢?今天我们就来讲解。  sqrt()函数为库函数,所以要包含对应的头文件,这个头文件包含了sqrt()函数的定义  下图中,x为要计算平方根的参数,sqrt()函数返回的是x的平方根,返回值

    2024年01月22日
    浏览(36)
  • FPGA verilog 简单的平方根求法

    用下面的平方根求法不需要乘法,只需简单的移位就能实现。 原理参照论文 A New Non-Restoring Square Root Algorithm and Its VLSI Implementations

    2024年02月04日
    浏览(34)
  • 数值分析——改进的平方根法(matlab实现)

    最近上数值分析学到了改进平方根法的原理,并最终借助matlab实现了运用该方法进行解题,浅浅的记录一下。 由于本人并非数学专业,不擅长公式的推导,在此仅将书中内容拍照整理,供大家参考,主要用的是图中圈的两个公式: 式中的D是正定矩阵,求解过程参考第一张图

    2024年02月11日
    浏览(113)
  • LeetCode每日一题——x 的平方根

    乍一看题目只需要算一个数的平方根,根据我们之前学的C语言我们能很快的想到使用sqrt,pow这类的math.h库函数,但是题目要求我们不能使用,那么我们便可以使用我们的数学思想,将给的整数拆成两个一样的数相乘。 代码实现: 运行结果:   PS:看到这里了,码字不易,给

    2024年03月23日
    浏览(46)
  • Java习题之实现平方根(sqrt)函数

    目录 前言 二分查找 牛顿迭代法 总结 🎁博主介绍:博客名为tq02,已学C语言、JavaSE,目前学了MySQL和JavaWeb 🎥学习专栏:  C语言        JavaSE      MySQL基础 🎄博主链接:tq02的博客_CSDN博客-C语言,Java,MySQL领域博主         可使用java.lang.Math类的 sqrt(double)方法 求平方根。

    2024年02月15日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包