本文对原文的代码做了一些注释和微调,为的是更加方便理解!!!
一、逐次逼近算法
逐次逼近算法流程如图 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个周期。
二、算法实现
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
可以看出,和在第二部分分析的一致,开方结果比输入延迟了n/2+1
个时钟周期,n为输入数据位宽。