4. 定点除法器设计
任务定义
设计一个基于恢复余数法的定点无符号除法器,输入为被除数 dividend 和除数 divisor(均为 8 位无符号整数),输出为 8 位商 quotient 和 8 位余数 remainder。要求通过自检程序验证多组随机输入下商与余数满足:dividend = quotient × divisor + remainder,且 remainder < divisor。
恢复余数法原理
恢复余数法按位迭代计算商。设被除数位宽为 N,则需迭代 N 次。每次迭代将当前余数左移一位并移入被除数的下一位,然后尝试减去除数:若减后结果非负,则商位为 1,保留减后结果作为新余数;若减后结果为负,则商位为 0,并恢复余数(加回除数)。从最高位开始逐位处理,最终得到完整商和余数。
RTL 实现
以下 Verilog 模块实现 8 位无符号恢复余数法除法器。采用状态机控制迭代过程,start 信号拉高一个周期启动计算,busy 信号指示运算中,done 信号在结果有效时拉高一个周期。
module restoring_divider #(
parameter WIDTH = 8
)(
input wire clk,
input wire rst_n,
input wire start,
input wire [WIDTH-1:0] dividend,
input wire [WIDTH-1:0] divisor,
output reg [WIDTH-1:0] quotient,
output reg [WIDTH-1:0] remainder,
output reg busy,
output reg done
);
localparam IDLE = 2'd0;
localparam CALC = 2'd1;
localparam FIN = 2'd2;
reg [1:0] state;
reg [WIDTH-1:0] dividend_reg;
reg [WIDTH-1:0] divisor_reg;
reg [WIDTH-1:0] quotient_reg;
reg [WIDTH-1:0] remainder_reg;
reg [WIDTH-1:0] temp_rem;
reg [3:0] bit_cnt; // 0~7 共8次迭代
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
busy <= 1'b0;
done <= 1'b0;
quotient <= {WIDTH{1'b0}};
remainder <= {WIDTH{1'b0}};
dividend_reg <= {WIDTH{1'b0}};
divisor_reg <= {WIDTH{1'b0}};
quotient_reg <= {WIDTH{1'b0}};
remainder_reg <= {WIDTH{1'b0}};
temp_rem <= {WIDTH{1'b0}};
bit_cnt <= 4'd0;
end
else begin
done <= 1'b0;
case (state)
IDLE: begin
if (start) begin
if (divisor == {WIDTH{1'b0}}) begin
// 除零保护:商置全1,余数置被除数
quotient <= {WIDTH{1'b1}};
remainder <= dividend;
done <= 1'b1;
end
else begin
dividend_reg <= dividend;
divisor_reg <= divisor;
quotient_reg <= {WIDTH{1'b0}};
remainder_reg <= {WIDTH{1'b0}};
bit_cnt <= 4'd0;
busy <= 1'b1;
state <= CALC;
end
end
end
CALC: begin
// 左移:余数左移1位,移入被除数当前最高位
temp_rem = {remainder_reg[WIDTH-2:0], dividend_reg[WIDTH-1]};
// 尝试减去除数
if (temp_rem >= divisor_reg) begin
remainder_reg <= temp_rem - divisor_reg;
quotient_reg <= {quotient_reg[WIDTH-2:0], 1'b1};
end
else begin
remainder_reg <= temp_rem; // 恢复余数(不减)
quotient_reg <= {quotient_reg[WIDTH-2:0], 1'b0};
end
// 被除数左移,丢弃已处理的最高位
dividend_reg <= {dividend_reg[WIDTH-2:0], 1'b0};
bit_cnt <= bit_cnt + 1'b1;
if (bit_cnt == WIDTH-1) begin
state <= FIN;
end
end
FIN: begin
quotient <= quotient_reg;
remainder <= remainder_reg;
busy <= 1'b0;
done <= 1'b1;
state <= IDLE;
end
default: state <= IDLE;
endcase
end
end
endmodule
测试平台与验证
以下 testbench 对 100 组随机输入进行验证。每组输入检查两个条件:条件1:dividend == quotient × divisor + remainder;条件2:remainder < divisor。同时覆盖除数为 0 的边界情况。若所有测试通过,打印 ALL TESTS PASSED;否则报告失败用例编号。
module tb_restoring_divider;
reg clk;
reg rst_n;
reg start;
reg [7:0] dividend;
reg [7:0] divisor;
wire [7:0] quotient;
wire [7:0] remainder;
wire busy;
wire done;
integer i;
integer errors;
reg [7:0] q_check;
reg [7:0] r_check;
restoring_divider #(.WIDTH(8)) dut (
.clk (clk),
.rst_n (rst_n),
.start (start),
.dividend (dividend),
.divisor (divisor),
.quotient (quotient),
.remainder(remainder),
.busy (busy),
.done (done)
);
// 时钟 10ns 周期
initial clk = 0;
always #5 clk = ~clk;
// 等待 done 信号
task wait_done;
@(posedge done);
@(posedge clk); // 同步到 done 后的时钟沿
endtask
integer seed;
initial begin
rst_n = 0;
start = 0;
dividend = 0;
divisor = 0;
errors = 0;
seed = 42;
#20 rst_n = 1;
// 测试除数为 0
@(posedge clk);
dividend = 8'd100;
divisor = 8'd0;
start = 1;
@(posedge clk);
start = 0;
wait_done();
if (quotient !== 8'hFF || remainder !== 8'd100) begin
$display("FAIL: divide-by-zero case, q=%0d r=%0d", quotient, remainder);
errors = errors + 1;
end
// 随机测试 100 组
for (i = 0; i < 100; i = i + 1) begin
@(posedge clk);
dividend = $random(seed) % 256;
divisor = ($random(seed) % 255) + 1; // 1~255,避免除零
start = 1;
@(posedge clk);
start = 0;
wait_done();
// 计算期望值
q_check = dividend / divisor;
r_check = dividend % divisor;
if (quotient !== q_check || remainder !== r_check) begin
$display("FAIL: case %0d: %0d / %0d => got q=%0d r=%0d, expected q=%0d r=%0d",
i, dividend, divisor, quotient, remainder, q_check, r_check);
errors = errors + 1;
end
end
if (errors == 0)
$display("ALL TESTS PASSED");
else
$display("TOTAL FAILURES: %0d", errors);
#20 $finish;
end
endmodule
运行与判定标准
使用支持 Verilog-2001 的仿真器(如 Icarus Verilog)运行:
iverilog -o div_tb restoring_divider.v tb_restoring_divider.v
vvp div_tb
观察仿真输出:若打印 ALL TESTS PASSED,说明 100 组随机用例及除零边界均通过,商和余数满足除法恒等式及余数小于除数的约束,验证通过。若出现 FAIL 行,则根据行内打印的输入、实际输出与期望值定位错误。
综合说明
本设计为纯组合逻辑迭代的状态机实现,8 位除法需要 8 个时钟周期完成计算(不含启动与收尾周期)。代码中 temp_rem 使用阻塞赋值,确保在同一个时钟沿内完成比较与更新,避免多周期路径。除零保护将商置为全 1、余数置为被除数,作为明确的错误标志。