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

文章介绍了使用Verilog硬件描述语言实现平方根计算的三种算法:二分迭代法、牛顿迭代法和逐次逼近法,并详细阐述了每种算法的原理和Verilog实现。同时,文章提到了状态机的设计和仿真结果,以及对算法性能的分析和优化建议。
摘要由CSDN通过智能技术生成

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

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

本文实现的算法包括二分迭代法、牛顿迭代法、逐次逼近法,前两种方法来源于数值计算方法,第三种方法类似于逐次渐进型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)。重复以上过程得到迭代公式x_{n+1}=x_{n}-\tfrac{f(x_{n})}{f'(x_{n})}。以下是算法过程:

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

第二,计算x_{n+1}=x_{n}-\tfrac{f(x_{n})}{f'(x_{n})}

第三,判断,若|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状态还应该输出一个计算器的计数使能,用于对计算所用时钟周期的计数。通过以上分析可以得到以下的状态转换图和输出信号表。

状态操作ff_encnt_endone

IDLE

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

 以下是数据通路电路图

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

 result的输出逻辑

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

 经过以上分析已经可以用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,使用除法器和加法器可以构成计算核心。如下图所示。

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

举个例子,被开方数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描述电路,定义模块名为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的每一位。

 

 

 定义模块名为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仿真的波形结果

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

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

五、分析与反思

二分法

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

牛顿法

性能最好,但是用了一个除法器。设计中为了满足存储中间数据的定宽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周期,对于很多数这是多余的,有兴趣的读者可以自己设计加上该逻辑。

*本文设计均单纯考虑实现算法不考虑面积时序功耗,仍有不足之处,欢迎读者给出评论建议。

  • 12
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值