Verilog 除法器是一种用于实现 Verilog 中的除法运算的电路。它可以将两个数字相除,并将结果作为一个整数或小数返回。Verilog 除法器可以用于多种应用,包括计算机图形学、信号处理、通信和控制。
Verilog 除法器的实现方式有很多,其中最常见的是采用位流除法器(Bit-Stream Divider)。该方法使用位流来表示被除数和除数,并在每一步中对位流进行相应的处理。在每一步中,都会根据当前位流的情况来决定是否要执行减法或者加法运算。
module divider (dividend, divisor, quotient, remainder); input [7:0] dividend; // 被除数 input [7:0] divisor; // 除数 output [7:0] quotient; // 商 output [7:0] remainder;// 余数 reg [7:0] quotient; // 存储商 reg [7:0] remainder; // 存储余数 reg [8:0] temp_dividend;// 存储被除数的临时变量 always @(*) begin // 每次有输入发生变化时都会执行此语句块 temp_dividend = {remainder, dividend};// 合并被除数和余数 if (temp_dividend >= divisor) begin // 如果大于或者等于除数 quotient = temp_dividend / divisor;// 进行除法运算 remainder = temp_dividend % divisor;// 进行取余运算 end else begin // 如果小于除数 quotient = 0;// 结果为 0 remainder = temp_dividend;// 直接将被除数作为余数 end end endmodule
和十进制除法类似,计算 27 除以 5 的过程如下所示:
除法运算过程如下:
需要说明的是,商的位宽应该与被除数保持一致,因为除数有可能为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):
// parameter M means the actual width of divisor
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):
//parameter N means the actual width of dividend
//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 中加入自校验,描述如下:
`timescale 1ns/1ns
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 个输入数据在延迟了和被除数相同位宽的周期数以后,输出了正确的除法结果。而且可流水式无延迟输出,符合设计。
点击这里下载源码
Arduino板上的引脚可以配置为输入或输出。我们将在这些模式下解释引脚的功能。重要的是要注意,大多数Arduino模拟引脚可以按照与...
菜单项模块允许你向应用或menu添加选项。查看menu例子。类:MenuItem使用下面的方法创建一个新的MenuItemnew MenuItem(options)o...
ipcRenderer模块是一个EventEmitter类的实例. 它提供了有限的方法,你可以从渲染进程向主进程发送同步或异步消息. 也可以收到主...