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、余数置为被除数,作为明确的错误标志。

恢复余数法除法器状态机 IDLE CALC (×8) FIN start & div≠0 bit_cnt==7 done=1 done 后回到 IDLE start & div==0 → 直接输出 q=FF, r=dividend 单次迭代数据流 (CALC 状态) remainder_reg [WIDTH-2:0] dividend_reg [WIDTH-1] (最高位) temp_rem (拼接结果) temp_rem ≥ divisor ? 是: 减除数, 商位=1 否: 恢复(不减), 商位=0 更新 remainder_reg 更新 quotient_reg