7.1 Verilog 除法器设计
除法器原理(定点)
和十进制除法类似,计算 27 除以 5 的过程如下所示:
除法运算过程如下:
- (1) 取被除数的高几位数据,位宽和除数相同(实例中是 3bit 数据)。
- (2) 将被除数高位数据与除数作比较,如果前者不小于后者,则可得到对应位的商为 1,两者做差得到第一步的余数;否则得到对应的商为 0,将前者直接作为余数。
- (3) 将上一步中的余数与被除数剩余最高位 1bit 数据拼接成新的数据,然后再和除数做比较。可以得到新的商和余数。
- (4) 重复过程 (3),直到被除数最低位数据也参与计算。
需要说明的是,商的位宽应该与被除数保持一致,因为除数有可能为1。所以上述手动计算除法的实例中,第一步做比较时,应该取数字 27 最高位 1 (3'b001) 与 3'b101 做比较。 根据此计算过程,设计位宽可配置的流水线式除法器,流水延迟周期个数与被除数位宽一致。
除法器设计
单步运算设计
单步除法计算时,单步被除数位宽(信号 dividend)需比原始除数(信号 divisor)位宽多 1bit 才不至于溢出。
为了便于流水,输出端需要有寄存器来存储原始的除数(信号 divisor 和 divisor_kp)和被除数信息(信号 dividend_ci 和 dividend_kp)。
单步的运算结果就是得到新的 1bit 商数据(信号 merchant)和余数(信号 remainder)。
为了得到最后的除法结果,新的 1bit 商数据(信号 merchant)还需要与上一周期的商结果(merchant_ci)进行移位累加。
单步运算单元设计如下(文件名 divider_cell.v):
实例
module divider_cell
# ( parameter N = 5 ,
parameter M = 3 )
(
input clk ,
input rstn ,
input en ,
input [M : 0 ] dividend ,
input [M - 1 : 0 ] divisor ,
input [N -M : 0 ] merchant_ci , //上一级输出的商
input [N -M - 1 : 0 ] dividend_ci , //原始除数
output reg [N -M - 1 : 0 ] dividend_kp , //原始被除数信息
output reg [M - 1 : 0 ] divisor_kp , //原始除数信息
output reg rdy ,
output reg [N -M : 0 ] merchant , //运算单元输出商
output reg [M - 1 : 0 ] remainder //运算单元输出余数
) ;
always @ ( posedge clk or negedge rstn ) begin
if ( !rstn ) begin
rdy <= 'b0 ;
merchant <= 'b0 ;
remainder <= 'b0 ;
divisor_kp <= 'b0 ;
dividend_kp <= 'b0 ;
end
else if (en ) begin
rdy <= 1'b1 ;
divisor_kp <= divisor ; //原始除数保持不变
dividend_kp <= dividend_ci ; //原始被除数传递
if (dividend >= { 1'b0 , divisor } ) begin
merchant <= (merchant_ci << 1 ) + 1'b1 ; //商为1
remainder <= dividend - { 1'b0 , divisor } ; //求余
end
else begin
merchant <= merchant_ci << 1 ; //商为0
remainder <= dividend ; //余数不变
end
end // if (en)
else begin
rdy <= 'b0 ;
merchant <= 'b0 ;
remainder <= 'b0 ;
divisor_kp <= 'b0 ;
dividend_kp <= 'b0 ;
end
end
endmodule
流水级例化
将单步计算的余数(信号 remainder)和原始被除数(信号 dividend)对应位的 1bit 数据重新拼接,作为新的单步被除数输入到下一级单步除法计算单元。
其中,被除数、除数、及商的数据信息也要在下一级运算单元中传递。
流水级模块例化完成除法的设计如下(文件名 divider_man.v):
实例
//using 29/5=5...4
module divider_man
# ( parameter N = 5 ,
parameter M = 3 ,
parameter N_ACT = M +N - 1 )
(
input clk ,
input rstn ,
input data_rdy , //数据使能
input [N - 1 : 0 ] dividend , //被除数
input [M - 1 : 0 ] divisor , //除数
output res_rdy ,
output [N_ACT -M : 0 ] merchant , //商位宽:N
output [M - 1 : 0 ] remainder ) ; //最终余数
wire [N_ACT -M - 1 : 0 ] dividend_t [N_ACT -M : 0 ] ;
wire [M - 1 : 0 ] divisor_t [N_ACT -M : 0 ] ;
wire [M - 1 : 0 ] remainder_t [N_ACT -M : 0 ] ;
wire [N_ACT -M : 0 ] rdy_t ;
wire [N_ACT -M : 0 ] merchant_t [N_ACT -M : 0 ] ;
//初始化首个运算单元
divider_cell # (.N (N_ACT ) , .M (M ) )
u_divider_step0
( .clk (clk ) ,
.rstn (rstn ) ,
.en (data_rdy ) ,
//用被除数最高位 1bit 数据做第一次单步运算的被除数,高位补0
.dividend ( { { (M ) { 1'b0 } } , dividend [N - 1 ] } ) ,
.divisor (divisor ) ,
.merchant_ci ( { (N_ACT -M + 1 ) { 1'b0 } } ) , //商初始为0
.dividend_ci (dividend [N_ACT -M - 1 : 0 ] ) , //原始被除数
//output
.dividend_kp (dividend_t [N_ACT -M ] ) , //原始被除数信息传递
.divisor_kp (divisor_t [N_ACT -M ] ) , //原始除数信息传递
.rdy (rdy_t [N_ACT -M ] ) ,
.merchant (merchant_t [N_ACT -M ] ) , //第一次商结果
.remainder (remainder_t [N_ACT -M ] ) //第一次余数
) ;
genvar i ;
generate
for (i = 1 ; i <=N_ACT -M ; i =i + 1 ) begin : sqrt_stepx
divider_cell # (.N (N_ACT ) , .M (M ) )
u_divider_step
(.clk (clk ) ,
.rstn (rstn ) ,
.en (rdy_t [N_ACT -M -i + 1 ] ) ,
.dividend ( {remainder_t [N_ACT -M -i + 1 ] , dividend_t [N_ACT -M -i + 1 ] [N_ACT -M -i ] } ) , //余数与原始被除数单bit数据拼接
.divisor (divisor_t [N_ACT -M -i + 1 ] ) ,
.merchant_ci (merchant_t [N_ACT -M -i + 1 ] ) ,
.dividend_ci (dividend_t [N_ACT -M -i + 1 ] ) ,
//output
.divisor_kp (divisor_t [N_ACT -M -i ] ) ,
.dividend_kp (dividend_t [N_ACT -M -i ] ) ,
.rdy (rdy_t [N_ACT -M -i ] ) ,
.merchant (merchant_t [N_ACT -M -i ] ) ,
.remainder (remainder_t [N_ACT -M -i ] )
) ;
end // block: sqrt_stepx
endgenerate
assign res_rdy = rdy_t [ 0 ] ;
assign merchant = merchant_t [ 0 ] ; //最后一次商结果作为最终的商
assign remainder = remainder_t [ 0 ] ; //最后一次余数作为最终的余数
endmodule
testbench
取被除数位宽为 5,除数位宽为 3,testbench 中加入自校验,描述如下:
实例
module test ;
parameter N = 5 ;
parameter M = 3 ;
reg clk ;
reg rstn ;
reg data_rdy ;
reg [N - 1 : 0 ] dividend ;
reg [M - 1 : 0 ] divisor ;
wire res_rdy ;
wire [N - 1 : 0 ] merchant ;
wire [M - 1 : 0 ] remainder ;
//clock
always begin
clk = 0 ; # 5 ;
clk = 1 ; # 5 ;
end
//driver
initial begin
rstn = 1'b0 ;
# 8 ;
rstn = 1'b1 ;
# 55 ;
@ ( negedge clk ) ;
data_rdy = 1'b1 ;
dividend = 25 ; divisor = 5 ;
# 10 ; dividend = 16 ; divisor = 3 ;
# 10 ; dividend = 10 ; divisor = 4 ;
# 10 ; dividend = 15 ; divisor = 1 ;
repeat ( 32 ) # 10 dividend = dividend + 1 ;
divisor = 7 ;
repeat ( 32 ) # 10 dividend = dividend + 1 ;
divisor = 5 ;
repeat ( 32 ) # 10 dividend = dividend + 1 ;
divisor = 4 ;
repeat ( 32 ) # 10 dividend = dividend + 1 ;
divisor = 6 ;
repeat ( 32 ) # 10 dividend = dividend + 1 ;
end
//对输入延迟,便于数据结果同周期对比,完成自校验
reg [N - 1 : 0 ] dividend_ref [N - 1 : 0 ] ;
reg [M - 1 : 0 ] divisor_ref [N - 1 : 0 ] ;
always @ ( posedge clk ) begin
dividend_ref [ 0 ] <= dividend ;
divisor_ref [ 0 ] <= divisor ;
end
genvar i ;
generate
for (i = 1 ; i <=N - 1 ; i =i + 1 ) begin
always @ ( posedge clk ) begin
dividend_ref [i ] <= dividend_ref [i - 1 ] ;
divisor_ref [i ] <= divisor_ref [i - 1 ] ;
end
end
endgenerate
//自校验
reg error_flag ;
always @ ( posedge clk ) begin
# 1 ;
if (merchant * divisor_ref [N - 1 ] + remainder != dividend_ref [N - 1 ] && res_rdy ) beginb //testbench 中可直接用乘号而不考虑运算周期
error_flag <= 1'b1 ;
end
else begin
error_flag <= 1'b0 ;
end
end
//module instantiation
divider_man # (.N (N ) , .M (M ) )
u_divider
(
.clk (clk ) ,
.rstn (rstn ) ,
.data_rdy (data_rdy ) ,
.dividend (dividend ) ,
.divisor (divisor ) ,
.res_rdy (res_rdy ) ,
.merchant (merchant ) ,
.remainder (remainder ) ) ;
//simulation finish
initial begin
forever begin
# 100 ;
if ( $time >= 10000 ) $finish ;
end
end
endmodule // test
仿真结果
由图可知,2 个输入数据在延迟了和被除数相同位宽的周期数以后,输出了正确的除法结果。而且可流水式无延迟输出,符合设计。
源码下载
Download