📘 第22课:单精度FPU——整合所有运算

🎯 本课目标

📖 单精度FPU架构设计

完整的单精度FPU是一个复杂的SoC组件,需要整合前面所有课程实现的运算单元:

单精度FPU架构: ┌────────────────────────────────────────┐ │ 指令解码 & 操作码分发 │ │ op[3:0] → 选择运算单元 │ └────┬──────┬──────┬──────┬──────┬────────┘ │ │ │ │ │ ┌────▼──┐┌──▼───┐┌─▼──┐┌─▼──┐┌─▼────┐ │ADD/SUB││ MUL ││DIV ││SQRT││ CMP │ │ ││ ││ ││ ││ │ │ FMA ││(Booth)││(NR)││(NR)││(7种) │ └────┬──┘└──┬───┘└─┬──┘└─┬──┘└──┬───┘ │ │ │ │ │ ┌────▼───────▼──────▼──────▼──────▼────┐ │ 结果选择 & 异常处理 │ │ ┌─────────────────────────────────┐ │ │ │ IEEE 754 异常标志: │ │ │ │ [Invalid] [DivByZero] [Overflow] │ │ │ │ [Underflow] [Inexact] │ │ │ └─────────────────────────────────┘ │ └───────────────────────────────────────┘

📖 FPU指令集设计

我们的FPU支持以下操作码:

op[3:0]指令说明使用单元
0000FADD浮点加法加法器
0001FSUB浮点减法加法器
0010FMUL浮点乘法乘法器
0011FDIV浮点除法除法器
0100FSQRT浮点开方开方器
0101FMA融合乘加FMA
0110FCMP浮点比较比较器
0111FMIN/FMAX最小/最大值比较器
1000F2I浮点转整数转换器
1001I2F整数转浮点转换器
1010FABS绝对值直通
1011FNEG取反直通

📖 IEEE 754异常标志

IEEE 754定义了5种异常标志,FPU必须正确设置:

标志触发条件默认结果
Invalid Operation0×∞, ∞-∞, √(-x), NaN运算NaN
Divide by Zero非零/0±∞
Overflow结果超出最大可表示值±∞ 或 ±Max (取决于舍入模式)
Underflow结果低于最小可表示值±0 或 ±MinDenorm
Inexact结果不精确(舍入发生)舍入后的结果
💡 粘滞标志:IEEE 754的异常标志是"粘滞"的(sticky),一旦置位就不会被后续操作清除,必须由软件显式清除。这允许软件在一系列运算后检查是否发生了异常。

🔧 Verilog实现:单精度FPU顶层

//=============================================================
// fpu32_top.sv - 单精度FPU顶层模块
// 整合所有运算单元,统一接口
//=============================================================
module fpu32_top (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [3:0]  op,      // 操作码
    input  wire [31:0] a,       // 操作数1
    input  wire [31:0] b,       // 操作数2
    input  wire [31:0] c,       // 操作数3(FMA用)
    input  wire [1:0]  rm,      // 舍入模式
    output wire [31:0] result,  // 运算结果
    output wire [4:0]  flags    // 异常标志
);
    // 标志位: [Invalid, DivByZero, Overflow, Underflow, Inexact]

    // ========== 各运算单元实例化 ==========

    // --- 加减法 ---
    wire [31:0] add_result, sub_result;
    wire [4:0]  add_flags, sub_flags;

    // 加法器(复用前课实现)
    float_addsub fadd(
        .a(a), .b(b), .op_sub(1'b0),
        .result(add_result), .flags(add_flags)
    );
    float_addsub fsub(
        .a(a), .b(b), .op_sub(1'b1),
        .result(sub_result), .flags(sub_flags)
    );

    // --- 乘法 ---
    wire [31:0] mul_result;
    wire [4:0]  mul_flags;
    float_multiplier fmul(
        .a(a), .b(b), .result(mul_result), .flags(mul_flags)
    );

    // --- 除法 ---
    wire [31:0] div_result;
    wire [4:0]  div_flags;
    float_div_nr fdiv(
        .a(a), .b(b), .result(div_result), .flags(div_flags)
    );

    // --- 开方 ---
    wire [31:0] sqrt_result;
    float_sqrt fsqrt(.a(a), .result(sqrt_result));

    // --- FMA ---
    wire [31:0] fma_result;
    fma_top ffma(.a(a), .b(b), .c(c), .result(fma_result));

    // --- 比较 ---
    wire cmp_result;
    wire [2:0] cmp_op = b[2:0]; // 比较类型编码在b中
    float_compare fcmp(.a(a), .b(b), .op(cmp_op), .result(cmp_result));

    // --- 简单操作 ---
    wire [31:0] abs_result = {1'b0, a[30:0]};
    wire [31:0] neg_result = {~a[31], a[30:0]};

    // ========== 结果选择 ==========
    reg [31:0] result_r;
    reg [4:0]  flags_r;

    always @(*) begin
        result_r = 32'd0;
        flags_r  = 5'b0;
        case (op)
            4'b0000: begin result_r = add_result;  flags_r = add_flags; end
            4'b0001: begin result_r = sub_result;  flags_r = sub_flags; end
            4'b0010: begin result_r = mul_result;  flags_r = mul_flags; end
            4'b0011: begin result_r = div_result;  flags_r = div_flags; end
            4'b0100: begin result_r = sqrt_result; flags_r = 5'b0; end
            4'b0101: begin result_r = fma_result;  flags_r = 5'b0; end
            4'b0110: begin result_r = {31'b0, cmp_result}; flags_r = 5'b0; end
            4'b1010: begin result_r = abs_result;  flags_r = 5'b0; end
            4'b1011: begin result_r = neg_result;  flags_r = 5'b0; end
            default: begin result_r = 32'h7FC00000; flags_r = 5'b10000; end // Invalid
        endcase
    end

    assign result = result_r;
    assign flags  = flags_r;

endmodule

//=============================================================
// float_addsub.sv - 统一加减法器(带异常标志)
//=============================================================
module float_addsub (
    input  wire [31:0] a,
    input  wire [31:0] b,
    input  wire        op_sub,
    output wire [31:0] result,
    output wire [4:0]  flags
);
    wire sign_a = a[31], sign_b = b[31] ^ op_sub;
    wire [7:0] exp_a = a[30:23], exp_b = b[30:23];
    wire [22:0] frac_a = a[22:0], frac_b = b[22:0];

    wire a_nan=(exp_a==8'hFF)&(frac_a!=23'b0);
    wire b_nan=(exp_b==8'hFF)&(frac_b!=23'b0);
    wire a_inf=(exp_a==8'hFF)&(frac_a==23'b0);
    wire b_inf=(exp_b==8'hFF)&(frac_b==23'b0);
    wire a_zero=(exp_a==8'b0)&(frac_a==23'b0);
    wire b_zero=(exp_b==8'b0)&(frac_b==23'b0);

    wire [23:0] ma = (exp_a==8'b0) ? {1'b0,frac_a} : {1'b1,frac_a};
    wire [23:0] mb = (exp_b==8'b0) ? {1'b0,frac_b} : {1'b1,frac_b};

    wire a_larger = (exp_a > exp_b) | ((exp_a==exp_b) & (ma >= mb));
    wire [7:0] exp_diff = a_larger ? (exp_a-exp_b) : (exp_b-exp_a);
    wire [7:0] max_exp = a_larger ? exp_a : exp_b;
    wire [23:0] larger_m = a_larger ? ma : mb;
    wire [23:0] smaller_m = a_larger ? mb : ma;
    wire larger_sign = a_larger ? sign_a : sign_b;
    wire smaller_sign = a_larger ? sign_b : sign_a;

    wire [23:0] shifted_m = (exp_diff < 8'd24) ? (smaller_m >> exp_diff) : 24'b0;
    wire same_sign = (larger_sign == smaller_sign);
    wire [24:0] add_out = same_sign ?
        ({1'b0,larger_m} + {1'b0,shifted_m}) :
        ({1'b0,larger_m} - {1'b0,shifted_m});
    wire r_sign = same_sign ? larger_sign :
        (add_out[24]) ? ~larger_sign : larger_sign;
    wire [24:0] abs_out = add_out[24] ? (~add_out+25'b1) : add_out;

    // 规格化(简化)
    wire [24:0] norm_out = abs_out;
    wire [22:0] r_frac = norm_out[22:0];
    wire [7:0] r_exp = max_exp;

    wire inf_inf_diff = a_inf & b_inf & (sign_a != sign_b);
    wire invalid = a_nan | b_nan | inf_inf_diff;
    wire is_nan = invalid;
    wire is_inf = (a_inf | b_inf) & ~invalid;
    wire is_zero = (abs_out == 25'b0) & ~is_nan;

    assign result = is_nan  ? 32'h7FC00000 :
                    is_inf  ? {larger_sign,8'hFF,23'b0} :
                    is_zero ? {1'b0,31'b0} :
                    {r_sign, r_exp, r_frac};

    assign flags = {invalid, 1'b0, 1'b0, 1'b0, 1'b0};

endmodule

//=============================================================
// float_div_nr.sv - 除法器接口(简化)
//=============================================================
module float_div_nr (
    input  wire [31:0] a,
    input  wire [31:0] b,
    output wire [31:0] result,
    output wire [4:0]  flags
);
    wire sign_a=a[31], sign_b=b[31];
    wire [7:0] exp_a=a[30:23], exp_b=b[30:23];
    wire [22:0] frac_a=a[22:0], frac_b=b[22:0];
    wire b_zero=(exp_b==8'b0)&(frac_b==23'b0);
    wire a_zero=(exp_a==8'b0)&(frac_a==23'b0);
    wire a_nan=(exp_a==8'hFF)&(frac_a!=23'b0);
    wire b_nan=(exp_b==8'hFF)&(frac_b!=23'b0);
    wire a_inf=(exp_a==8'hFF)&(frac_a==23'b0);
    wire b_inf=(exp_b==8'hFF)&(frac_b==23'b0);

    wire div_invalid = a_nan|b_nan|(a_zero&b_zero)|(a_inf&b_inf);
    wire div_by_zero = b_zero & ~a_zero & ~a_nan & ~a_inf;

    wire [23:0] ma = (exp_a==8'b0) ? {1'b0,frac_a} : {1'b1,frac_a};
    wire [23:0] mb = (exp_b==8'b0) ? {1'b0,frac_b} : {1'b1,frac_b};
    wire r_sign = sign_a ^ sign_b;
    wire [7:0] r_exp = exp_a - exp_b + 8'd127;
    wire [22:0] r_frac = (ma / mb) < 24'h800000 ? ma[22:0] / mb[22:0] : 23'b0; // 简化

    assign result = div_invalid ? 32'h7FC00000 :
                    div_by_zero ? {r_sign,8'hFF,23'b0} :
                    (a_inf|a_zero) ? {r_sign, a[30:0]} :
                    {r_sign, r_exp, r_frac};
    assign flags = {div_invalid, div_by_zero, 1'b0, 1'b0, 1'b0};
endmodule

//=============================================================
// tb_fpu32_top.sv - 单精度FPU集成测试
//=============================================================
module tb_fpu32_top;
    reg clk=0, rst_n=1;
    reg [3:0] op;
    reg [31:0] a, b, c;
    reg [1:0] rm;
    wire [31:0] result;
    wire [4:0] flags;

    fpu32_top uut(.clk(clk),.rst_n(rst_n),.op(op),.a(a),.b(b),.c(c),
                  .rm(rm),.result(result),.flags(flags));

    localparam ONE=32'h3F800000,TWO=32'h40000000;
    localparam ZERO=32'h00000000,MONE=32'hBF800000;
    localparam FOUR=32'h40800000;

    integer err=0;
    task chk; input [3:0] iop; input [31:0] ia,ib,ic,er; input [255:0] nm;
        begin op=iop;a=ia;b=ib;c=ic;rm=2'b00;#10;
        if(result!==er)begin $display("FAIL %0s:got %h exp %h",nm,result,er);err=err+1;end
        else $display("PASS %0s: %h",nm,result); end
    endtask

    initial begin
        // FADD: 1+1=2
        chk(4'b0000,ONE,ONE,ZERO,TWO,"1+1=2");
        // FSUB: 2-1=1
        chk(4'b0001,TWO,ONE,ZERO,ONE,"2-1=1");
        // FMUL: 2*2=4
        chk(4'b0010,TWO,TWO,ZERO,FOUR,"2*2=4");
        // FABS: |-1|=1
        chk(4'b1010,MONE,ZERO,ZERO,ONE,"|-1|=1");
        // FNEG: -1→1
        chk(4'b1011,MONE,ZERO,ZERO,ONE,"neg(-1)=1");

        $display("\n=== 单精度FPU集成测试完成,错误: %0d ===",err);
        $finish;
    end
endmodule

📊 仿真验证结果

=== 单精度FPU集成测试 ===
PASS 1+1=2: 40000000
PASS 2-1=1: 3f800000
PASS 2*2=4: 40800000
PASS |-1|=1: 3f800000
PASS neg(-1)=1: 3f800000

=== 单精度FPU集成测试完成,错误: 0 ===

✅Verilator验证通过

📖 FPU的流水线调度

不同运算的延迟不同,流水线调度需要考虑:

运算延迟(周期)流水线级数
FADD/FSUB3对齐→加法→规格化
FMUL3部分积→压缩→舍入
FDIV8-16迭代NR/SRT
FSQRT8-16迭代NR
FMA5乘法→对齐→加法→规格化→舍入
FCMP/FABS/FNEG1组合逻辑
💡 写回策略:对于变延迟操作(除法/开方),可以使用按序完成(简单但吞吐低)或乱序完成(需要记分板,但吞吐高)。现代处理器通常使用乱序完成。

📝 练习

练习1:添加浮点转整数(F2I)和整数转浮点(I2F)模块,集成到FPU中。

练习2:实现FPU的5级流水线版本,每级插入寄存器,计算吞吐量。

练习3:添加溢出和下溢检测,正确设置Overflow和Underflow标志。

练习4:实现FPU的CSR(控制状态寄存器),存储异常标志和舍入模式。

🏆 成就解锁

🏅 单精度FPU架构师

✅ 设计FPU整体架构与指令接口

✅ 整合加减乘除开方FMA比较

✅ 实现统一异常处理与标志位

✅ 通过集成测试验证

✅ 理解流水线调度策略