除法器原理
和十进制除法类似,计算 27 除以 5 的过程如下所示:
除法运算过程如下:
(1) 取被除数的高几位数据,位宽和除数相同(实例中是 3bit 数据)。
(2) 将被除数高位数据与除数作比较,如果前者不小于后者,则可得到对应位的商为 1,两者做差得到第一步的余数;否则得到对应的商为 0,将前者直接作为余数。
(3) 将上一步中的余数与被除数剩余最高位 1bit 数据拼接成新的数据,然后再和除数做比较。可以得到新的商和余数。
(4) 重复过程 (3),直到被除数最低位数据也参与计算。
需要说明的是,商的位宽应该与被除数保持一致,因为除数有可能为1。所以上述手动计算除法的实例中,第一步做比较时,应该取数字 27 最高位 1 (3’b001) 与 3’b101 做比较。 根据此计算过程,设计位宽可配置的流水线式除法器,流水延迟周期个数与被除数位宽一致。
除法器设计
单独除法器设计
单步被除数位宽(信号 dividend)需比原始除数(信号 divisor)位宽多 1bit 才不至于溢出。为了便于流水,输出端需要有寄存器来存储原始的除数(信号divisor_orang_r)和被除数信息(信号 dividend_orang_r)。单步的运算结果就是得到新的 1bit 商数据(信号 merchant)和余数(信号 remainder)。为了得到最后的除法结果,新的 1bit 商数据(信号 merchant)还需要与上一周期的商结果(merchant_last)进行移位累加。单步运算单元设计如下(文件名 divider_cell.v):
module divider_cell#(
parameter N = 5,
parameter M = 3
)
(
input wire clk,
input wire rst_n,
input wire en,
input wire [N-1:0] dividend_orang, //原始被除数
input wire [M-1:0] divisor_orang, //原始除数
output reg [N-1:0] dividend_orang_r, //原始被除数
output reg [M-1:0] divisor_orang_r, //原始除数
input wire [M:0] dividend, //本轮被除数
input wire [N-1:0] merchant_last, //上一轮商结果
output reg valid, //本轮商有效标识
output reg [N-1:0] merchant, //本轮商
output reg [M-1:0] reminder //本轮余数
);
always@(posedge clk) begin
if(en) begin
dividend_orang_r <= dividend_orang;
divisor_orang_r <= divisor_orang;
end
else begin
dividend_orang_r <= 'b0;
divisor_orang_r <= 'b0;
end
end
always@(posedge clk) begin
if(!rst_n) begin
valid <= 1'b0;
end
else begin
valid <= en;
end
end
always@(posedge clk) begin
if(!rst_n) begin
merchant <= 'b0;
reminder <= 'b0;
end
else begin
if(en) begin
if(dividend >= {1'b0, divisor_orang}) begin
merchant <= (merchant_last<<1) + 1'b1;
reminder <= dividend - {1'b0, divisor_orang};
end
else begin
merchant <= merchant_last<<1;
reminder <= dividend[M-1:0];
end
end
else begin
merchant <= 'b0;
reminder <= 'b0;
end
end
end
endmodule
流水线例化
流水线除法器能够实现流水计算,在N个周期连续计算出N个结果。
将单步计算的余数(信号 remainder)和原始被除数(信号 dividend)对应位的 1bit 数据重新拼接,作为新的单步被除数输入到下一级单步除法计算单元。其中,被除数、除数、及商的数据信息也要在下一级运算单元中传递。流水级模块例化完成除法的设计如下(文件名 pipeline_divider.v):
module pipeline_divider_top#(
parameter N = 32, //被除数位宽
parameter M = 32 //除数位宽
)
(
input wire clk,
input wire rst_n,
input wire en,
input wire [N-1:0] dividend,
input wire [M-1:0] divisor,
output wire valid,
output wire [N-1:0] merchant,
output wire [M-1:0] reminder
);
wire [N-1:0] dividend_orang [N-1:0]; //原始被除数、除数连线,需要N条
wire [M-1:0] divisor_orang [N-1:0];
wire [N-1:0] valid_cell; //valid连线,需要N条
wire [N-1:0] merchant_cell [N-1:0]; //商结果,需要N条
wire [M-1:0] reminder_cell [N-1:0];
divider_cell#(
.N ( N ),
.M ( M )
)u_divider_cell(
.clk ( clk ),
.rst_n ( rst_n ),
.en ( en ),
.dividend_orang ( dividend ),
.divisor_orang ( divisor ),
.dividend_orang_r ( dividend_orang[N-1] ),
.divisor_orang_r ( divisor_orang[N-1] ),
.dividend ( { {M{1'b0}}, dividend[N-1] } ),
.merchant_last ( 'b0 ),
.valid ( valid_cell[N-1] ),
.merchant ( merchant_cell[N-1] ),
.reminder ( reminder_cell[N-1] )
);
genvar i;
generate
for(i = 0; i < N-1; i = i + 1) begin: divider_cell_pipeline
divider_cell#(
.N ( N ),
.M ( M )
)u_divider_cell(
.clk ( clk ),
.rst_n ( rst_n ),
.en ( valid_cell[i+1] ),
.dividend_orang ( dividend_orang[i+1] ),
.divisor_orang ( divisor_orang[i+1] ),
.dividend_orang_r ( dividend_orang[i] ),
.divisor_orang_r ( divisor_orang[i] ),
.dividend ( {reminder_cell[i+1], dividend_orang[i+1][i]} ),
.merchant_last ( merchant_cell[i+1] ),
.valid ( valid_cell[i] ),
.merchant ( merchant_cell[i] ),
.reminder ( reminder_cell[i] )
);
end
endgenerate
assign valid = valid_cell[0];
assign merchant = merchant_cell[0];
assign reminder = reminder_cell[0];
endmodule
非流水线除法器
这种除法器在N周期内只能完成一次除法计算,但只需要一个单步除法器。通过计数器来控制单步除法器单元的输入,从而实现移位除法计算。
module single_divider#(
parameter N = 32, //被除数位宽
parameter M = 32 //除数位宽
)
(
input wire clk,
input wire rst_n,
input wire en,
output reg ready,
input wire [N-1:0] dividend,
input wire [M-1:0] divisor,
output reg valid,
output wire [N-1:0] merchant,
output wire [M-1:0] reminder
);
reg [ 7:0] cnt;
reg busy;
//divider_cell connevtion
wire [N-1:0] dividend_orang_i;
wire [M-1:0] divisor_orang_i;
wire [N-1:0] dividend_orang_o;
wire [M-1:0] divisor_orang_o;
wire en_i;
wire valid_o;
wire [M:0] dividend_i;
wire [N-1:0] merchant_i;
wire [N-1:0] merchant_o;
wire [M-1:0] reminder_o;
always@(*) begin
ready = ~busy;
end
always@(posedge clk) begin
if(!rst_n) begin
valid <= 1'b0;
end
else begin
if(cnt == N-1) begin
valid <= 1'b1;
end
else begin
valid <= 1'b0;
end
end
end
always@(posedge clk) begin
if(!rst_n) begin
cnt <= 8'd0;
end
else begin
if(cnt == N) begin
cnt <= 8'd0;
end
else if(en || busy) begin
cnt <= cnt + 8'd1;
end
else ;
end
end
always@(posedge clk) begin
if(!rst_n) begin
busy <= 1'b0;
end
else begin
if(en) begin
busy <= 1'b1;
end
else if(cnt == N) begin
busy <= 1'b0;
end
else ;
end
end
assign en_i = (en || busy) && ~valid;
assign dividend_orang_i = en? dividend : dividend_orang_o;
assign divisor_orang_i = en? divisor : divisor_orang_o;
assign dividend_i = en? {{M{1'b0}}, dividend[N-1]} :
(busy && ~valid)? {reminder_o, dividend_orang_o[8'd31 - cnt]} : 'b0;
assign merchant_i = (busy && ~valid)? merchant_o : 'b0;
assign merchant = valid? merchant_o : 'b0;
assign reminder = valid? reminder_o : 'b0;
divider_cell#(
.N ( N ),
.M ( M )
)u_divider_cell(
.clk ( clk ),
.rst_n ( rst_n ),
.en ( en_i ),
.dividend_orang ( dividend_orang_i ),
.divisor_orang ( divisor_orang_i ),
.dividend_orang_r ( dividend_orang_o ),
.divisor_orang_r ( divisor_orang_o ),
.dividend ( dividend_i ),
.merchant_last ( merchant_i ),
.valid ( valid_o ),
.merchant ( merchant_o ),
.reminder ( reminder_o )
);
endmodule