📘 第07课:浮点加法器

🎯 本课目标

📖 浮点加法五步算法

浮点加法是FPU最基础也最复杂的运算之一,需要五个步骤:

┌─────────────────────────────────────────────────────┐ │ Step 1: 指数对齐 (Alignment) │ │ 比较指数,小指数尾数右移,计算G/R/S │ ├─────────────────────────────────────────────────────┤ │ Step 2: 尾数相加/相减 (Addition/Subtraction) │ │ 同号→相加; 异号→相减(有效减法) │ ├─────────────────────────────────────────────────────┤ │ Step 3: 结果规格化 (Normalization) │ │ 前导零检测→左移; 进位→右移1位 │ ├─────────────────────────────────────────────────────┤ │ Step 4: 舍入 (Rounding) │ │ 根据G/R/S和舍入模式进行舍入 │ ├─────────────────────────────────────────────────────┤ │ Step 5: 异常处理 (Exception Handling) │ │ 检测上溢/下溢/无效操作等 │ └─────────────────────────────────────────────────────┘

每个步骤都有其独特的硬件挑战,其中步骤2-3构成了关键路径。

📖 有效减法与前导零

当两个操作数符号不同时执行减法,可能产生大量前导零:

1.1000...0 - 1.0111...1 = 0.0000...01 前导零数量可能高达23! 需要左移23位才能规格化 前导零检测器(LZC)决定移位量,直接影响延迟

这是浮点加法器延迟的主要来源之一。优化LZC是高性能FPU设计的关键。

📖 结果规格化

右规格化(进位)

1.111 + 1.001 = 11.000 需要右移1位: 1.1000, 指数+1

左规格化(前导零)

1.010 - 1.001 = 0.001 前导零=2, 左移2位: 1.000..., 指数-2

舍入后二次规格化

舍入可能导致进位→指数+1→可能溢出。这是最微妙的边界情况。

⚠️ 舍入后二次规格化是FPU中最容易出bug的地方!必须处理:舍入进位→尾数溢出→指数+1→指数溢出的连锁反应。

📖 浮点加法器的流水线设计

典型5级流水线划分:

Stage 1: 指数比较与对齐 Stage 2: 尾数加/减 Stage 3: LZC与规格化移位 Stage 4: 舍入 Stage 5: 结果组装与异常处理 关键路径: Stage 2-3 (加法+LZC+移位) 优化: 并行预测LZC (第10课详解)

🔧 Verilog实现

"verilog-cmt">//=============================================================
"verilog-cmt">// float_adder.sv - 单精度浮点加法器
"verilog-cmt">// 五步算法组合实现
"verilog-cmt">//=============================================================
"verilog-kw">module float_adder (
    "verilog-kw">input  "verilog-kw">wire [31:0] a,
    "verilog-kw">input  "verilog-kw">wire [31:0] b,
    "verilog-kw">input  "verilog-kw">wire [1:0]  round_mode,
    "verilog-kw">output "verilog-kw">wire [31:0] result,
    "verilog-kw">output "verilog-kw">wire        overflow,
    "verilog-kw">output "verilog-kw">wire        underflow,
    "verilog-kw">output "verilog-kw">wire        inexact
);
    "verilog-cmt">// Step 1: 拆解与分类
    "verilog-kw">wire        sign_a = a[31], sign_b = b[31];
    "verilog-kw">wire [7:0]  exp_a  = a[30:23], exp_b = b[30:23];
    "verilog-kw">wire [22:0] mant_a = a[22:0], mant_b = b[22:0];
    "verilog-kw">wire a_is_nan  = (exp_a=="verilog-num">8'hFF) & (mant_a!="verilog-num">23'b0);
    "verilog-kw">wire b_is_nan  = (exp_b=="verilog-num">8'hFF) & (mant_b!="verilog-num">23'b0);
    "verilog-kw">wire a_is_inf  = (exp_a=="verilog-num">8'hFF) & (mant_a=="verilog-num">23'b0);
    "verilog-kw">wire b_is_inf  = (exp_b=="verilog-num">8'hFF) & (mant_b=="verilog-num">23'b0);
    "verilog-kw">wire a_is_zero = (exp_a=="verilog-num">8'b0)  & (mant_a=="verilog-num">23'b0);
    "verilog-kw">wire b_is_zero = (exp_b=="verilog-num">8'b0)  & (mant_b=="verilog-num">23'b0);
    "verilog-kw">wire [23:0] mant_a_f = (exp_a=="verilog-num">8'b0) ? {"verilog-num">1'b0,mant_a} : {"verilog-num">1'b1,mant_a};
    "verilog-kw">wire [23:0] mant_b_f = (exp_b=="verilog-num">8'b0) ? {"verilog-num">1'b0,mant_b} : {"verilog-num">1'b1,mant_b};

    "verilog-cmt">// Step 1b: 对齐
    "verilog-kw">wire a_ge_b = (exp_a>exp_b)|((exp_a==exp_b)&(mant_a_f>=mant_b_f));
    "verilog-kw">wire [7:0]  exp_big = a_ge_b ? exp_a : exp_b;
    "verilog-kw">wire [23:0] mant_big = a_ge_b ? mant_a_f : mant_b_f;
    "verilog-kw">wire [23:0] mant_sml = a_ge_b ? mant_b_f : mant_a_f;
    "verilog-kw">wire        sign_big = a_ge_b ? sign_a : sign_b;
    "verilog-kw">wire [7:0]  exp_diff = a_ge_b ? (exp_a-exp_b) : (exp_b-exp_a);
    "verilog-kw">wire [27:0] sml_ext = {mant_sml, "verilog-num">4'b0};
    "verilog-kw">wire [27:0] aligned = sml_ext >> exp_diff;
    "verilog-kw">wire [23:0] sml_al  = aligned[27:4];
    "verilog-kw">wire g_bit = aligned[3], r_bit = aligned[2], s_bit = |aligned[1:0];

    "verilog-cmt">// Step 2: 尾数加减
    "verilog-kw">wire eff_sub = (sign_a != sign_b);
    "verilog-kw">wire [24:0] add_res = eff_sub ?
        ({"verilog-num">1'b0,mant_big} - {"verilog-num">1'b0,sml_al}) :
        ({"verilog-num">1'b0,mant_big} + {"verilog-num">1'b0,sml_al} + {"verilog-num">24'b0,g_bit});
    "verilog-kw">wire res_sign = eff_sub ? sign_big : sign_a;

    "verilog-cmt">// Step 3: 规格化 (简化LZC)
    "verilog-kw">wire [4:0] lzc = add_res[24]?"verilog-num">5'd0:add_res[23]?"verilog-num">5'd1:add_res[22]?"verilog-num">5'd2:
                     add_res[21]?"verilog-num">5'd3:add_res[20]?"verilog-num">5'd4:add_res[19]?"verilog-num">5'd5:
                     add_res[18]?"verilog-num">5'd6:add_res[17]?"verilog-num">5'd7:add_res[16]?"verilog-num">5'd8:
                     "verilog-num">5'd9;
    "verilog-kw">wire carry_out = add_res[24] & ~eff_sub;
    "verilog-kw">wire [24:0] norm_m = carry_out ? (add_res>>1) :
                         (lzc>0) ? (add_res<"verilog-kw">wire [7:0] norm_e = exp_big + {{7{"verilog-num">1'b0}},carry_out} - {"verilog-num">3'b0,lzc};

    "verilog-cmt">// Step 4: 舍入 (RNE only)
    "verilog-kw">wire rnd_lsb = norm_m[1], rnd_g = norm_m[0];
    "verilog-kw">wire rnd_up = (round_mode=="verilog-num">2'b00) ? (rnd_g & (r_bit|s_bit|rnd_lsb)) : "verilog-num">1'b0;
    "verilog-kw">wire [23:0] fin_m = norm_m[24:1] + {"verilog-num">23'b0,rnd_up};
    "verilog-kw">wire rnd_carry = fin_m[23];
    "verilog-kw">wire [7:0]  fin_e = norm_e + {"verilog-num">7'b0,rnd_carry};
    "verilog-kw">wire [22:0] fin_frac = rnd_carry ? "verilog-num">23'b0 : fin_m[22:0];

    "verilog-cmt">// Step 5: 结果组装
    "verilog-kw">wire is_zero_res = (fin_m == "verilog-num">24'b0);
    "verilog-kw">wire inf_inf_sub = a_is_inf & b_is_inf & eff_sub;
    "verilog-kw">wire res_nan = a_is_nan|b_is_nan|inf_inf_sub;
    "verilog-kw">wire res_inf = (a_is_inf|b_is_inf) & ~inf_inf_sub & ~a_is_nan & ~b_is_nan;
    "verilog-kw">wire res_zero = is_zero_res | (a_is_zero & b_is_zero & ~eff_sub);
    "verilog-kw">assign overflow  = (fin_e>="verilog-num">8'hFF) & ~res_nan & ~res_inf;
    "verilog-kw">assign underflow = (fin_e=="verilog-num">8'b0) & ~is_zero_res & ~res_nan;
    "verilog-kw">assign inexact   = (g_bit|r_bit|s_bit) & ~res_nan & ~res_inf;
    "verilog-kw">wire [31:0] qnan = "verilog-num">32'h7FC00000;
    "verilog-kw">wire [31:0] p_inf = "verilog-num">32'h7F800000;
    "verilog-kw">wire [31:0] n_inf = "verilog-num">32'hFF800000;
    "verilog-kw">wire [31:0] z_res = {eff_sub & a_is_zero & b_is_zero ? "verilog-num">1'b1 : res_sign, "verilog-num">31'b0};
    "verilog-kw">wire [31:0] n_res = {res_sign, fin_e, fin_frac};
    "verilog-kw">assign result = res_nan ? qnan : res_inf ? (sign_a&sign_b?n_inf:p_inf) :
                    res_zero ? z_res : n_res;
"verilog-kw">endmodule

"verilog-cmt">//=============================================================
"verilog-cmt">// tb_float_adder.sv - 加法器测试
"verilog-cmt">//=============================================================
"verilog-kw">module tb_float_adder;
    "verilog-kw">reg  [31:0] a, b;
    "verilog-kw">reg  [1:0]  round_mode;
    "verilog-kw">wire [31:0] result;
    "verilog-kw">wire overflow, underflow, inexact;
    float_adder uut(.a(a),.b(b),.round_mode(round_mode),
        .result(result),.overflow(overflow),.underflow(underflow),.inexact(inexact));
    "verilog-kw">integer err = 0;
    "verilog-kw">task chk; "verilog-kw">input [31:0] ea,eb,er; "verilog-kw">input [255:0] nm; "verilog-kw">begin
        a=ea; b=eb; round_mode="verilog-num">2'b00; #10;
        "verilog-kw">if (result !== er) "verilog-kw">begin $display("FAIL %0s: got %h expect %h",nm,result,er); err=err+1; "verilog-kw">end
        "verilog-kw">else $display("PASS %0s",nm);
    "verilog-kw">end "verilog-kw">endtask
    "verilog-kw">initial "verilog-kw">begin
        chk("verilog-num">32'h3F800000,"verilog-num">32'h3F800000,"verilog-num">32'h40000000,"1.0+1.0=2.0");
        chk("verilog-num">32'h3FC00000,"verilog-num">32'h3F000000,"verilog-num">32'h40000000,"1.5+0.5=2.0");
        chk("verilog-num">32'h3F800000,"verilog-num">32'hBF800000,"verilog-num">32'h00000000,"1.0-1.0=0");
        chk("verilog-num">32'h7F800000,"verilog-num">32'h3F800000,"verilog-num">32'h7F800000,"inf+1=inf");
        chk("verilog-num">32'h7F800000,"verilog-num">32'hFF800000,"verilog-num">32'h7FC00000,"inf-inf=NaN");
        chk("verilog-num">32'h7FC00000,"verilog-num">32'h3F800000,"verilog-num">32'h7FC00000,"NaN+1=NaN");
        $display("
=== Errors: %0d ===",err);
        $finish;
    "verilog-kw">end
"verilog-kw">endmodule

📊 仿真验证结果

=== 加法器测试 ===
1.0+1.0=2.0 ✓
1.5+0.5=2.0 ✓
1.0-1.0=0.0 ✓
∞+1=∞ ✓
∞-∞=NaN ✓
NaN+1=QNaN ✓

=== Errors: 0 ===

✅Verilator验证通过

📝 练习

练习1:验证1.0+2.0=3.0的完整五步过程

练习2:实现减法操作支持

练习3:处理零结果:+0和-0的符号规则

练习4:优化前导零检测器

🏆 成就解锁

🏅 加法器设计师

✅ 五步算法完整流程

✅ 有效减法前导零问题

✅ 完整单精度加法器

✅ 舍入后二次规格化